codex

package
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 19, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package codex provides hook-based capture for OpenAI Codex sessions.

One provider covers both distributions of the product: the standalone `codex` CLI (Homebrew, npm) and the Codex desktop app, which embeds the same runtime over stdio JSON-RPC. Both honor user-global hook configuration at ~/.codex/hooks.json and the `[features] hooks = true` flag in ~/.codex/config.toml.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Provider

type Provider struct{}

Provider implements hooks.HookProvider for OpenAI Codex.

func New added in v0.5.1

func New() *Provider

New returns the Codex hook provider for explicit registration via providers.NewHookRegistry().

func (*Provider) AreHooksInstalled

func (p *Provider) AreHooksInstalled(ctx context.Context, repoRoot string) bool

AreHooksInstalled reports whether ~/.codex/hooks.json contains at least one Semantica-owned entry. Detection runs on the file alone - trust state is best-effort and a missing trust entry only means the user has not yet acknowledged the hooks, not that they are absent.

func (*Provider) BuildHookEvents

func (p *Provider) BuildHookEvents(ctx context.Context, event *hooks.Event, bs api.BlobPutter) ([]broker.RawEvent, error)

BuildHookEvents implements hooks.DirectHookEmitter for Codex.

The dispatcher routes only PromptSubmitted and ToolStepCompleted through this path. Codex lifecycle events (SessionStart -> SessionOpened, Stop -> AgentCompleted) reach the dispatcher through ParseHookEvent and are handled by the dispatcher's own lifecycle cases.

func (*Provider) DisplayName

func (p *Provider) DisplayName() string

func (*Provider) HookBinary

func (p *Provider) HookBinary(ctx context.Context, repoRoot string) (string, error)

HookBinary returns the binary path Codex would execute for any one of our installed hooks. Health checks use this to verify `semantica` is still reachable via exec.LookPath on the user's machine.

func (*Provider) InstallHooks

func (p *Provider) InstallHooks(ctx context.Context, repoRoot string, binaryPath string) (int, error)

InstallHooks writes the Semantica hook configuration into Codex's user-global config directory. The repoRoot argument is unused for the install itself (Codex hooks are not per-repo) but kept to satisfy the HookProvider contract; the cwd preflight gates per-repo behavior at capture time instead.

On success, the user has:

  • ~/.codex/hooks.json with four entries (SessionStart, UserPromptSubmit, PostToolUse, Stop) pointing at the Semantica binary. Existing user hook entries under the same events are preserved; Semantica's entries are appended after them.
  • ~/.codex/config.toml updated with [features] hooks = true and one [hooks.state.*] trusted_hash per installed hook, so Codex does not prompt for hook review on the next session. Trust keys reflect the actual (groupIndex, hookIndex) where Semantica's entries written, which can be non-zero when the file already contained unrelated hooks for the same event.

User configuration in config.toml (model pins, plugin blocks, marketplace declarations, project trust levels) is preserved across the round-trip. Comments and original key ordering are not retained because the TOML round-trip rewrites the file through a map; callers that need a comment-preserving editor should add one upstream and share it across providers.

Re-running is safe: identical commands at identical positions produce identical content and identical trust hashes, so the file ends up byte-equivalent.

func (*Provider) IsAvailable

func (p *Provider) IsAvailable() bool

IsAvailable reports whether Codex is present on this machine. The standalone CLI installs `codex` on PATH; the desktop app drops a bundle at /Applications/Codex.app on macOS plus a per-user state directory at ~/.codex. Either signal is enough to surface Codex as a known provider.

func (*Provider) Name

func (p *Provider) Name() string

func (*Provider) ParseHookEvent

func (p *Provider) ParseHookEvent(ctx context.Context, hookName string, stdin io.Reader) (*hooks.Event, error)

ParseHookEvent translates Codex hook stdin into a normalized Event. Returns nil for hook names that have no capture analogue today so the dispatcher skips them quietly.

Hook event mapping:

  • session_start -> SessionOpened (lifecycle no-op in the dispatcher)
  • user_prompt_submit -> PromptSubmitted
  • post_tool_use -> ToolStepCompleted
  • stop -> AgentCompleted

Codex does not currently emit a session-end signal. Capture state is removed at every AgentCompleted (turn end), matching how Claude Code's Stop is handled.

func (*Provider) ReadFromOffset

func (p *Provider) ReadFromOffset(ctx context.Context, transcriptRef string, offset int, bs api.BlobPutter) ([]broker.RawEvent, int, error)

ReadFromOffset is a no-op. Codex rollout files (~/.codex/sessions/...) are not a stable contract and have observed non-strict JSONL where record strings embed raw newlines. The provider relies exclusively on hook payloads. If a fallback read path becomes useful, it should use a streaming JSON decoder rather than line-based splitting.

func (*Provider) ShouldCapture

func (p *Provider) ShouldCapture(ctx context.Context, payload []byte, activeRepos []broker.RegisteredRepo) (bool, error)

ShouldCapture decides whether a Codex hook invocation should run any downstream side effects, based on the session's working directory.

User-global hooks at ~/.codex/hooks.json fire for every Codex session on the machine, including sessions in repositories the user has not registered with Semantica. The capture entrypoint's default broker-wide gate ("any active repo?") is too permissive for that surface: a Codex session in /tmp would otherwise leak prompts, file edits, and shell commands into capture state belonging to an unrelated registered repo.

We require the session's cwd to resolve to a git repository root that matches one of the active broker entries by canonical path. If either resolution fails - no enclosing repo, no matching canonical entry - ShouldCapture returns false and the caller exits before parsing stdin, opening the blob store, writing to the broker, or appending to the hook-error log.

func (*Provider) TranscriptOffset

func (p *Provider) TranscriptOffset(ctx context.Context, transcriptRef string) (int, error)

TranscriptOffset returns 0 unconditionally. Codex's rollout files are not used for capture today; hooks supply every event we need.

func (*Provider) UninstallHooks

func (p *Provider) UninstallHooks(ctx context.Context, repoRoot string) error

UninstallHooks removes Semantica's entries from the user-global configuration. Other tools' hooks - including any non-Semantica entries the user may have added by hand - are preserved.

The function reads the current hooks.json to recover the exact command strings and positions of Semantica's entries before pruning the file, then removes only the trust entries whose hash matches what those commands produced. Trust entries whose hash differs (e.g. modified by the user or written by a different tool) are left untouched.

Codex hooks are user-global, so a disable on any one repo removes the hooks for all repos. Re-running enable on another registered repo restores them. This is a deliberate trade for keeping the install surface symmetric across CLI and desktop sessions; users with multiple active repos who only want to disable Codex on one should rely on the cwd preflight (a deregistered repo will not produce capture events) rather than running `disable --providers codex`.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL