Skip to content

feat(codex): SubagentStop block surface#13

Merged
VDP89 merged 2 commits into
mainfrom
feat/codex-subagent-stop
Jun 5, 2026
Merged

feat(codex): SubagentStop block surface#13
VDP89 merged 2 commits into
mainfrom
feat/codex-subagent-stop

Conversation

@VDP89

@VDP89 VDP89 commented Jun 5, 2026

Copy link
Copy Markdown
Owner

What

Increment 2 of deepening the Codex hook integration: the SubagentStop block surface. fscar init --adapter codex now also registers SubagentStop, and a scar with event_type = HookEventType.SUBAGENT_STOP can 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)

SubagentStop blocks with the top-level feedback form, same as the other non-PreToolUse events:

{"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: add HookEventType.SUBAGENT_STOP, map the event (+ lowercase alias), and add it to WANTED_EVENTS (so install / doctor / the manifest pick it up automatically).

Two deliberate differences from PermissionRequest

  • Exit code 2 is a documented block path for SubagentStop (the doc shows decision:"block" or exit 2 with reason on stderr), so run_hook returns the normal block exit code — no special-casing like PermissionRequest needed.
  • Filtering is by agent_type, not tool_name. SubagentStop carries no tool, so the engine tool-matcher filter is intentionally not extended to it. A scar reads payload.raw["agent_type"] / payload.raw["last_assistant_message"] in its matches().

Verification

  • fscar init --adapter codex registers SubagentStop in .codex/hooks.json + manifest (via WANTED_EVENTS).
  • End-to-end: a SubagentStop scar that blocks when last_assistant_message lacks "coverage" emits decision:"block" + reason and exits 2.
  • Gate: 189 passed (+3), ruff check fscars cookbook tests clean, mypy fscars strict clean.

Review focus

  1. Confirm the decision:"block" + top-level reason shape is what Codex expects for SubagentStop (vs the continue:false alternative the doc also mentions — I chose decision:"block" as the documented primary).
  2. Confirm exit 2 alongside the JSON block is correct for SubagentStop (the doc lists it; this is the opposite of the PermissionRequest decision in feat(codex): PermissionRequest deny surface #12).
  3. Confirm not extending the engine tool_matchers filter to SubagentStop is right (it filters on agent_type, which a scar handles in matches()).

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

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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>
@VDP89

VDP89 commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

P2 fixed (commit 41558b8)

Verified the generated schema directly (subagent-stop.command.output.schema.json): top-level only — continue / decision / reason / stopReason / suppressOutput / systemMessage, additionalProperties: false, no hookSpecificOutput. My reuse of the generic feedback branch (which appends hookSpecificOutput) produced output the schema would reject.

Fix: emit_output now special-cases SUBAGENT_STOP — emits decision/reason on block and systemMessage as the only context channel, never hookSpecificOutput. Regression updated to assert hookSpecificOutput is absent, plus a non-block case that routes context to systemMessage. Docs + CHANGELOG note the strict schema.

PreToolUse / PostToolUse / other events are unchanged (still echo hookSpecificOutput). exit-2-on-block for SubagentStop unchanged (it's documented for this surface).

Gate: 190 passed, ruff clean, mypy strict clean. Re-requesting review.

@VDP89
VDP89 merged commit 736eb1f into main Jun 5, 2026
9 checks passed
@VDP89
VDP89 deleted the feat/codex-subagent-stop branch June 5, 2026 13:59
VDP89 added a commit that referenced this pull request Jun 5, 2026
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>
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