feat(codex): SubagentStop block surface#13
Conversation
Increment 2 of deepening the Codex hooks. fscar init --adapter codex now also registers SubagentStop. A scar with event_type=SUBAGENT_STOP can keep a subagent from stopping by blocking, via the top-level decision:"block"+reason shape (e.g. 'report batch coverage before you stop'). Unlike PermissionRequest, exit code 2 IS a documented block path here, so a block exits 2 (handled by the existing emit_output feedback branch + run_hook). Subagent-lifecycle fields (agent_type, last_assistant_message, ...) are preserved on payload.raw for the scar's matches(); filtering is by agent_type, not tool_name, so the engine tool-matcher filter is intentionally not extended. Adds HookEventType.SUBAGENT_STOP, the event mapping + lowercase alias, and the event to WANTED_EVENTS (install/doctor/manifest follow). Shape verified against developers.openai.com/codex/hooks. Docs + CHANGELOG. Gate: 189 passed, ruff clean, mypy strict clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e20b0c0d9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "PostToolUse", | ||
| "PermissionRequest", | ||
| "Stop", | ||
| "SubagentStop", |
There was a problem hiding this comment.
Return SubagentStop JSON blocks with exit 0
Adding SubagentStop here routes blocking scars through the existing run_hook path, which writes the JSON {"decision":"block","reason":...} to stdout and then returns exit code 2 for every non-PermissionRequest block. I checked the current Codex hooks doc: for SubagentStop, JSON stdout is the contract when the hook exits 0, while the exit-2 alternative requires the continuation reason on stderr. Since this hook emits no stderr, a subagent coverage gate can be ignored or continued without the requested prompt whenever it blocks; this surface needs a Codex-specific exit-0 path (or stderr output) before registration.
Useful? React with 👍 / 👎.
…put (PR #13) P2: the generic feedback branch appended hookSpecificOutput, but the official SubagentStop output schema is additionalProperties:false with no hookSpecificOutput property (continue/decision/reason/stopReason/ suppressOutput/systemMessage only). emit_output now special-cases SUBAGENT_STOP: emit decision/reason on block and systemMessage as the only context channel, never hookSpecificOutput. Verified against the generated schema (codex-rs/hooks/schema/generated/subagent-stop.command.output.schema.json). Regression updated to assert no hookSpecificOutput + a non-block case routing context to systemMessage. Docs + CHANGELOG note the strict schema. Gate: 190 passed, ruff clean, mypy strict clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P2 fixed (commit
|
fscar init --adapter codex now registers every documented Codex hook event: the three blocking surfaces (PreToolUse, PermissionRequest, SubagentStop) plus the three context surfaces (SubagentStart, PreCompact, PostCompact), each matched to its generated output schema. Bundles PRs #13 + #14. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Increment 2 of deepening the Codex hook integration: the
SubagentStopblock surface.fscar init --adapter codexnow also registersSubagentStop, and a scar withevent_type = HookEventType.SUBAGENT_STOPcan keep a subagent from stopping by blocking — e.g. a "the batch subagent must report coverage before it stops" gate.Follows #12 (
PermissionRequest).SubagentStart/Pre-PostCompact(context injection, lower value) are the intended next increment.Shape (verified against the official doc)
SubagentStopblocks with the top-level feedback form, same as the other non-PreToolUseevents:{"decision": "block", "reason": "report batch coverage before you stop."}This is already what
emit_output's existing feedback branch produces, so the adapter change is just: addHookEventType.SUBAGENT_STOP, map the event (+ lowercase alias), and add it toWANTED_EVENTS(soinstall/doctor/ the manifest pick it up automatically).Two deliberate differences from PermissionRequest
SubagentStop(the doc showsdecision:"block"or exit 2 with reason on stderr), sorun_hookreturns the normal block exit code — no special-casing like PermissionRequest needed.agent_type, nottool_name.SubagentStopcarries no tool, so the engine tool-matcher filter is intentionally not extended to it. A scar readspayload.raw["agent_type"]/payload.raw["last_assistant_message"]in itsmatches().Verification
fscar init --adapter codexregistersSubagentStopin.codex/hooks.json+ manifest (viaWANTED_EVENTS).SubagentStopscar that blocks whenlast_assistant_messagelacks "coverage" emitsdecision:"block"+ reason and exits 2.ruff check fscars cookbook testsclean,mypy fscarsstrict clean.Review focus
decision:"block"+ top-levelreasonshape is what Codex expects forSubagentStop(vs thecontinue:falsealternative the doc also mentions — I chosedecision:"block"as the documented primary).SubagentStop(the doc lists it; this is the opposite of the PermissionRequest decision in feat(codex): PermissionRequest deny surface #12).tool_matchersfilter toSubagentStopis right (it filters onagent_type, which a scar handles inmatches()).Version not bumped —
[Unreleased]; cut is your call (could pair with the next increment or stand alone like #12 → 0.8.0).🤖 Generated with Claude Code