Skip to content

feat(adapters): codex T4 — hook wiring for automatic session capture#396

Merged
plind-junior merged 1 commit into
vouchdev:testfrom
dripsmvcp:feat/codex-capture-hook
Jul 6, 2026
Merged

feat(adapters): codex T4 — hook wiring for automatic session capture#396
plind-junior merged 1 commit into
vouchdev:testfrom
dripsmvcp:feat/codex-capture-hook

Conversation

@dripsmvcp

Copy link
Copy Markdown
Contributor

follows #395: with vouch capture ingest-codex available, codex can self-capture the way claude-code T4 does. one design correction against the ticket, discovered while checking the current codex cli: the notify setting the ticket proposed is a restricted key — codex honours it only in user-global ~/.codex/config.toml and ignores it in project-local layers, and the #179 rule forbids a project-scoped install from touching home-directory state. codex's hooks system is the project-local replacement (notify itself is now a legacy shim over it): <project>/.codex/hooks.json is loaded in trusted projects and fires Stop when a turn completes, with a json payload on stdin carrying session_id, cwd, and transcript_path.

so T4 ships .codex/hooks.json registering a Stop hook that runs vouch capture ingest-codex --hook. the file installs through the existing json_merge machinery — codex's hooks.json shape is the same event/group/command structure as claude-code's settings hooks — which gives the ticket's "never silently overwrite an existing user hook" for free: a pre-existing hooks.json is deep-merged into, the user's own hooks stay, and re-runs don't duplicate ours. as a side effect this drops the planned dependency on the toml_merge pr (#392) for the wiring itself, though this branch still stacks on the codex adapter chain for the shared manifest.

the new --hook mode reads the stop payload from stdin, resolves the session's rollout (transcript_path when it points at a jsonl, else by the session id embedded in the rollout filename), ingests idempotently, and exits 0 no matter what — the same never-break-the-host rule capture observe follows; garbage stdin, a missing kb, or an unresolvable session are all silent no-ops.

Stop fires per turn, not per session end, so the finalize policy the ticket left open is resolved as idempotent re-ingest: the #387 dedup guard now updates rather than duplicates — a session that grew since the last ingest refreshes its still-PENDING proposal in place (same proposal id, updated summary, proposal.page.update audit event), an unchanged rollout reports already-ingested, and a proposal the human already approved or rejected is never resurrected or mutated. storage gains update_proposal, a pure-I/O in-place rewrite of a pending proposal file; the only-pending rule lives with the caller, keeping storage logic-free.

wiring only — everything still lands through propose_page, nothing touches approve(). tests cover the install paths (fresh write, merge next to a user hook, idempotent re-run without duplication) and the hook-driven ingest (payload handling, session-id resolution, grown-session refresh, decided-proposal protection, audit event, exit-0 guarantees with garbage stdin and no kb). verified end-to-end against a real codex 0.142.0 rollout through a stdin stop payload: proposal filed, re-run no-op, garbage exits 0.

stacks on #392/#393/#394 (shared codex manifest) plus #395 (the ingest command) — merge after them; the diff reduces to the T4 change once they land.

closes #388

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b61f5579-1aa9-47f6-b483-588b2e093368

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added cli command line interface adapters agent host adapters and install manifests storage kb storage, migrations, schemas, and proposals tests tests and fixtures size: XL 1000 or more changed non-doc lines labels Jul 6, 2026
@plind-junior plind-junior requested a review from Copilot July 6, 2026 07:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds full Codex adapter integration up through T4 by wiring project-local hooks to automatically ingest Codex session rollouts into vouch’s existing review-gated capture flow, including idempotent “refresh pending proposal” behavior for per-turn Stop events.

Changes:

  • Introduces Codex rollout parsing + ingestion (vouch capture ingest-codex, plus --hook mode) and pending-proposal refresh semantics.
  • Extends the Codex adapter tiers (T1–T4): TOML deep-merge for .codex/config.toml, fenced AGENTS.md snippet, project-local skills, and .codex/hooks.json Stop hook wiring via json_merge.
  • Adds storage support for in-place rewrites of existing pending proposals (update_proposal) and expands installer logic (fenced refresh + TOML merge utilities).

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/test_install_adapter.py Adds coverage for Codex T2/T3/T4 install behaviors, fenced refresh, json_merge hooks, and toml_merge config behavior.
tests/test_codex_rollout.py Adds end-to-end tests for rollout parsing, ingest behavior, hook payload handling, and --latest resolution.
tests/fixtures/codex/rollout-no-meta.jsonl Adds fixture for missing-session-meta error path.
tests/fixtures/codex/rollout-basic.jsonl Adds representative rollout fixture used for parser/ingest coverage.
src/vouch/storage.py Adds KBStore.update_proposal() for in-place pending proposal rewrites.
src/vouch/install_adapter.py Adds toml_merge, fenced refresh behavior, TOML merge + minimal TOML serializer, and install path wiring.
src/vouch/codex_rollout.py Implements rollout parsing, ingest/dedup/update logic, and Stop-hook payload handling.
src/vouch/cli.py Adds capture ingest-codex CLI command with --latest and --hook modes, and wires CodexRolloutError into CLI error handling.
adapters/codex/README.md Documents Codex T3 skills and T4 automatic capture via hooks.json, including rationale vs notify.
adapters/codex/install.yaml Defines Codex tiered install manifest (T1 toml_merge, T2 fenced snippet, T3 skills, T4 hooks.json json_merge).
adapters/codex/hooks.json Provides the default Stop hook command wiring to vouch capture ingest-codex --hook.
adapters/codex/AGENTS.md.snippet Adds Codex AGENTS.md snippet establishing vouch usage + review-gate invariants.

Comment thread src/vouch/codex_rollout.py Outdated
Comment on lines +285 to +289
sessions = (codex_home or default_codex_home()) / "sessions"
if not sessions.is_dir() or not session_id.strip():
return None
matches = sorted(sessions.rglob(f"rollout-*-{session_id}.jsonl"), reverse=True)
return matches[0] if matches else None
Comment thread src/vouch/codex_rollout.py Outdated
Comment on lines +92 to +93
verbs = {verb for verb, _ in matches}
verb = "Created" if verbs == {"Add"} else "Edited"
Comment thread tests/test_install_adapter.py Outdated
Comment on lines +510 to +516
(codex_dir / "hooks.json").write_text(json.dumps({
"hooks": {
"Stop": [
{"hooks": [{"type": "command", "command": "my-own-stop-hook"}]}
]
}
}))
Comment thread tests/test_codex_rollout.py Outdated
day.mkdir(parents=True)
target = day / "rollout-2026-07-01T09-00-00-sess-42.jsonl"
target.write_text("{}", encoding="utf-8")
(day / "rollout-2026-07-01T10-00-00-sess-43.jsonl").write_text("{}")
@dripsmvcp dripsmvcp force-pushed the feat/codex-capture-hook branch from 0f3de2a to 205c5e4 Compare July 6, 2026 08:46
@github-actions github-actions Bot added the mcp mcp, jsonl, and http surfaces label Jul 6, 2026
@dripsmvcp

Copy link
Copy Markdown
Contributor Author

addressed the copilot review: find_rollout_by_session_id now globs a fixed pattern and matches the session id as a literal filename suffix, so a payload carrying glob metacharacters (*, ?, [) can't widen the search; and the flagged test writes now pin encoding="utf-8". added a glob-injection regression test.

this branch was rebuilt on the updated #394 and the fixed #395 ingest, so it also carries those reviews' fixes.

with `vouch capture ingest-codex` available, codex can self-capture
the way claude-code T4 does. the ticket assumed the `notify` setting
in config.toml, but codex only honours notify in user-global config —
it is a restricted key in project-local layers — and the vouchdev#179 rule
forbids touching ~/.codex. codex's hooks system is the project-local
equivalent: `.codex/hooks.json` (loaded in trusted projects) fires
Stop when a turn completes, with a payload carrying the session id and
transcript path. T4 ships that file through json_merge, so an existing
user hooks.json is deep-merged into, never overwritten, and re-runs
don't duplicate the hook.

the handler is a new --hook mode on ingest-codex: read the stop
payload from stdin, resolve the session's rollout (transcript_path
when present, else by session id in the rollout filename), ingest
idempotently, and exit 0 no matter what — the same never-break-the-
host rule capture observe follows.

stop fires per turn, not per session end, so the finalize policy the
ticket left open is resolved as idempotent re-ingest: the dedup guard
now refreshes a session's still-PENDING proposal in place when the
rollout grew (same id, updated summary, audit-logged), reports an
unchanged rollout as a no-op, and never resurrects a proposal a human
already decided. storage gains update_proposal, a pure-I/O in-place
rewrite of a pending proposal file.

wiring only — everything still lands through propose_page; nothing
touches approve().

closes vouchdev#388
@dripsmvcp dripsmvcp force-pushed the feat/codex-capture-hook branch from 205c5e4 to 79ed4c1 Compare July 6, 2026 09:01
@github-actions github-actions Bot added size: M 200-499 changed non-doc lines and removed mcp mcp, jsonl, and http surfaces size: XL 1000 or more changed non-doc lines labels Jul 6, 2026
@plind-junior plind-junior merged commit d323635 into vouchdev:test Jul 6, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adapters agent host adapters and install manifests cli command line interface size: M 200-499 changed non-doc lines storage kb storage, migrations, schemas, and proposals tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants