Skip to content

codex: SessionStart hook at parity with Claude Code - #60

Merged
subygan merged 1 commit into
mainfrom
codex-sessionstart-parity
Oct 1, 2026
Merged

subygan merged 1 commit into
mainfrom
codex-sessionstart-parity

Conversation

@subygan

@subygan subygan commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Codex's SessionStart hook (parsec up --session-start) did much less than Claude Code's parsec hook SessionStart, and what it did print leaked into the model's context. This brings it to parity.

Before: the hook printed raw text to stdout. Codex surfaces that to the model as a developer message, so lines like proxy already listening on 127.0.0.1:… — nothing to do landed in context. The model also never got the curation note (fold-marker contract), even though Codex traffic is curated (responses.rs fold-back). That's the read-spiral the note exists to prevent.

After: Codex 0.157.1's embedded session-start.command.output schema takes the same fields as Claude Code (systemMessage, suppressOutput, hookSpecificOutput.additionalContext). The new hook::codex_session_start prints exactly that JSON object and nothing else:

Claude Code Codex (before) Codex (after)
Curation note → model (startup/clear/compact, entitled) ✅ ❌ ✅
Staged trim → model, one-shot (startup/clear) ✅ raw stdout, also on resume ✅
No-key banner (fresh startup) ✅ ❌ ✅ worded for Codex
Lifetime savings note (fresh startup) ✅ ❌ ✅ $parsec-savings
Proxy upgrade / revival notices ✅ status text leaked to model ✅ as systemMessage
Fires on clear / compact ✅ ❌ (startup|resume) ✅

Other changes

  • The Codex hook block passes --port, so the hook revives the port the Codex provider actually routes to (it can differ from Claude Code's).
  • If something that isn't parsec holds the port, or anything else fails, the hook now reports it as a message and exits 0 instead of erroring.
  • apikey::gate_banner and statusline::lifetime_note take a trim::Source, so their text matches the harness.
  • setup::up drops its session_start parameter; main.rs sends --session-start to the new handler.

Deliberately not ported

  • First-run auto-setup: it downloads the embedder and writes Claude Code settings.
  • No-reread state reset: Codex has no PreToolUse gate recording reads.
  • Claude Code's parsec disable switch: parsec disable codex removes the hook entirely.

Upgrade note

The JSON output takes effect as soon as the binary updates. The wider matcher and --port arrive only after re-running parsec setup codex. This is noted in the CHANGELOG.

Test plan

  • cargo fmt --check, cargo clippy -p parsec-proxy --all-targets -- -D warnings
  • New trim_cli tests: the curation note on startup/clear/compact but not on resume or when unentitled; the trim skipped on resume and delivered on clear (alongside the note); the banner as a Codex-worded systemMessage; stdout is always valid hook JSON
  • Existing Codex trim test updated to read additionalContext
  • Manual: a real Codex session to check how the branded systemMessage panel renders (it's laid out for Claude Code's display)

Pre-existing failures, unrelated: noreread::tests::grant_is_per_file_cross_surface_and_refunds and compound_command_passes_whole_on_reissue fail on clean main too. Everything else in parsec-proxy passes.

`parsec up --session-start` (Codex's SessionStart hook) now emits hook
JSON in the shape Codex 0.15x accepts (session-start.command.output):
the curation note and any staged trim as additionalContext, the key
banner, lifetime note and proxy upgrade/revival notices as systemMessage.

Previously it printed raw text, which Codex surfaces to the model as a
developer message: proxy status lines leaked into context, and the
model never got the fold-marker contract despite Codex traffic being
curated.

- hook::codex_session_start mirrors the Claude Code SessionStart branch;
  stdout carries only the JSON object, and failures become messages
  (exit 0) instead of errors.
- Banner and lifetime note are worded per harness (trim::Source::label,
  savings_cmd → `$parsec-savings` on Codex).
- Codex hook block matches startup|resume|clear|compact and passes
  --port, so it revives the port the Codex provider routes to.
- Trim is no longer consumed on resume (same one-shot rule as Claude).
@subygan
subygan merged commit e97063c into main Oct 1, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant