Skip to content

feat(hooks): wire session_start, session_stop, user_prompt_submit#214

Merged
emal-avala merged 2 commits intomainfrom
feat/wire-unfired-hooks
Apr 23, 2026
Merged

feat(hooks): wire session_start, session_stop, user_prompt_submit#214
emal-avala merged 2 commits intomainfrom
feat/wire-unfired-hooks

Conversation

@emal-avala
Copy link
Copy Markdown
Member

Summary

Three hook events — `SessionStart`, `SessionStop`, and `UserPromptSubmit` — are declared in the config schema, listed by `/hooks`, and documented in the module header, but were never actually fired from the agent loop. User-configured hooks for these events would silently never run.

This PR wires all three:

  • `UserPromptSubmit` fires inside `run_turn_with_sink` right after the user message is pushed to history and before `PreTurn`. Hooks see the full prompt text (not the 200-char preview `PreTurn` gets) so compliance scanners and audit logs have the real input.
  • `SessionStart` fires once per engine lifetime, immediately after `load_hooks`. Context includes session id, cwd, and active model.
  • `SessionStop` fires at every clean exit:
    • One-shot path: fires before `std::process::exit`, so non-zero exits still invoke user hooks.
    • Interactive REPL path: fires after the loop returns.
    • Scheduled executor: fires after the turn completes.

Also wires these for the scheduled-task executor so `agent schedule` one-shots get the same lifecycle as interactive sessions.

Test plan

  • `cargo clippy --workspace --tests --no-deps -- -D warnings` — clean
  • `cargo test -p agent-code-lib --lib hooks::` — 4 new async tests pass
  • `cargo test -p agent-code-lib --lib query::` — 15 existing query tests still pass
  • New tests use a shell hook that writes to a temp file, proving each event dispatches and that events don't cross-fire

Three hook events were declared in the config schema, listed by /hooks,
and documented in the module header — but never actually called from the
agent loop. Users configuring a session_start audit log, a session_stop
cleanup script, or a user_prompt_submit redaction guard would have seen
nothing happen at runtime.

This wires all three:

- UserPromptSubmit fires inside run_turn_with_sink, right after the
  user message is pushed to history and before PreTurn. Hooks get the
  full prompt text (not the 200-char preview PreTurn receives) so
  compliance audits / scanners see the real input.

- SessionStart fires once per engine lifetime, after load_hooks. It
  receives session id, cwd, and active model in the JSON context, so
  hooks can tag logs or load per-project secrets.

- SessionStop fires at every clean exit: the one-shot path (even on
  non-zero exit codes, by firing before std::process::exit), the REPL
  path after the loop returns, and the scheduled-executor one-shot
  path after the turn completes. Hook context includes turn count,
  total cost, and total tokens for summary reporting.

Adds 4 async unit tests in hooks::tests using a shell hook that touches
a temp file, verifying each of the three events dispatches correctly
and that events don't cross-fire (SessionStop dispatch with only a
SessionStart hook registered leaves the probe file empty).
@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.

The new hooks::tests module uses bash subprocesses to prove each event
dispatches. On Windows CI, `bash` resolves to WSL, which isn't
installed on the runner — the tests fail with a WSL install-distribution
error. The Shell hook action itself invokes `bash -c` in production,
so these tests exercise a code path that is fundamentally unix-only
anyway. Gate the whole tests module on cfg(unix).
@emal-avala emal-avala merged commit eb03352 into main Apr 23, 2026
14 checks passed
@emal-avala emal-avala deleted the feat/wire-unfired-hooks branch April 23, 2026 18:29
@emal-avala emal-avala mentioned this pull request Apr 23, 2026
5 tasks
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