Skip to content

feat(hooks): Stop event — fires once per response when yielding to user#223

Merged
emal-avala merged 1 commit intomainfrom
feat/hook-stop-event
Apr 23, 2026
Merged

feat(hooks): Stop event — fires once per response when yielding to user#223
emal-avala merged 1 commit intomainfrom
feat/hook-stop-event

Conversation

@emal-avala
Copy link
Copy Markdown
Member

@emal-avala emal-avala commented Apr 23, 2026

Summary

Adds a Stop hook event that fires exactly once per user-visible response, right after the final PostTurn and before control returns to the REPL / one-shot exit. PostTurn fires once per LLM round-trip — a multi-step turn with N tool calls fires PostTurn N+1 times, which is too noisy for auto-commit / transcript-shipping / desktop-notification workflows that want "agent finished; now react."

Context payload

{
  "session_id": "...",
  "turn_count": 3,
  "last_assistant_message": "..."
}

last_assistant_message is extracted via a new last_assistant_text helper that walks the message history backward, skipping pure tool-use assistant turns so the hook sees the actual user-visible reply — not the internal "I'm about to call Bash" step.

Wiring

  • HookEvent::Stop added to the schema enum with snake_case serde
  • fire_stop_hooks(last_assistant_message: Option<&str>) async method on QueryEngine
  • Called from run_turn_with_sink's no-more-tools completion branch, right after PostTurn
  • HOOK_EVENT_CATALOG, format_hook_event, parse_hook_event updated
  • hooks/mod.rs module docs updated

Test plan

  • cargo clippy --workspace --tests --no-deps -- -D warnings — clean
  • cargo test -p agent-code-lib --lib hooks::tests — 6 pass
  • cargo test -p agent-code-lib --lib query::tests::last_assistant_text — 4 pass
  • cargo test -p agent-code-lib --lib config::schema::tests::hook_event_serde_roundtrip — 10 pass
  • cargo test -p agent-code --bin agent commands::tests::parse_hook_event_accepts_stop — pass

6 new tests:

  1. Schema serde round-trip (stop"stop")
  2. run_hooks dispatches for Stop
  3. parse_hook_event accepts "stop" / "Stop" / "STOP"
  4. last_assistant_text: empty history returns None
  5. last_assistant_text: returns most recent assistant text / joins multiple blocks
  6. last_assistant_text: skips pure-tool-use assistant messages

Follow-up

SubagentStop needs the parent's HookRegistry plumbed into ToolContext (our subagents are subprocesses, so the parent fires on child exit). Separate PR once this lands.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Adds a `Stop` hook event that fires exactly once per user-visible
response, right after the final `PostTurn` and before control returns
to the REPL / one-shot exit. `PostTurn` fires once per LLM round-trip
— a multi-step turn with N tool calls fires `PostTurn` N+1 times,
which is too noisy for auto-commit / transcript-shipping /
desktop-notification workflows that want "agent finished; now react."

Context payload:

  {
    "session_id": "...",
    "turn_count": N,
    "last_assistant_message": "..."  // or null if pure tool-use
  }

`last_assistant_message` is extracted via a new `last_assistant_text`
helper that walks the message history backward, skipping pure
tool-use assistant turns so the hook sees the actual user-visible
reply.

Wiring:
- HookEvent::Stop added to schema enum with snake_case serde
- fire_stop_hooks(last_assistant_message) async method on QueryEngine
- Called from run_turn_with_sink's no-more-tools completion branch,
  after PostTurn fires
- HOOK_EVENT_CATALOG, format_hook_event, parse_hook_event updated
- hooks/mod.rs module docs updated

Tests: 6 new
- Schema serde round-trip (stop <-> "stop")
- run_hooks dispatches for Stop (async tokio test)
- parse_hook_event accepts "stop" / "Stop" / "STOP"
- last_assistant_text: empty history returns None
- last_assistant_text: returns most recent assistant text
- last_assistant_text: joins multiple text blocks
- last_assistant_text: skips pure-tool-use assistant messages
@emal-avala emal-avala force-pushed the feat/hook-stop-event branch from 7259a80 to c6aadbb Compare April 23, 2026 20:47
@emal-avala emal-avala merged commit 1dd81f1 into main Apr 23, 2026
14 checks passed
@emal-avala emal-avala deleted the feat/hook-stop-event branch April 23, 2026 21:43
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