feat(hooks): add 4 new hook events to match Claude Code / OpenCode / pi#2548
Merged
dgageot merged 3 commits intodocker:mainfrom Apr 28, 2026
Merged
Conversation
a5a80d6 to
3b8ba08
Compare
trungutt
previously approved these changes
Apr 28, 2026
3b8ba08 to
948a1f8
Compare
rumpl
previously approved these changes
Apr 28, 2026
948a1f8 to
7d14f65
Compare
Adds the four hook events that all three competitor coding agents expose but docker-agent did not: - user_prompt_submit: fires once per user message, after submission and before the first model call. Can block the prompt or contribute transient additional_context. Skipped for sub-sessions whose kick-off message is synthesised by the runtime. - pre_compact: fires before context-window compaction (manual / auto / overflow / tool_overflow trigger). Can cancel compaction or append guidance to the compaction prompt. - subagent_stop: fires when a sub-agent (transfer_task, background agent, skill sub-session) finishes. Runs against the parent's hooks executor so handlers placed on the orchestrator see every child. - permission_request: fires just before the runtime would prompt the user to approve a tool call. Hooks can short-circuit the prompt by returning permission_decision=allow|deny, mirroring pre_tool_use. Also fixes the post_tool_use documentation everywhere (Go doc, schema, docs/, example) to state that it fires on both success and failure; tool_response.is_error distinguishes the two. Adds five contract-widening tests pinning the new events' wire-format contract. Assisted-By: docker-agent
Three issues surfaced by code review: * subagent_stop now fires on the error path of both sub-session helpers. Previously the hook was placed *after* the for-range childEvents loop, which an ErrorEvent skipped via early return \u2014 so handlers configured to observe sub-agent completions silently missed every failed run. Move the dispatch into a defer at the top of each helper so it fires for both success and failure (handlers can detect failure via empty stop_response or by correlating with the parent's error event). * Clarify the EventPermissionRequest doc comment. The old text said the hook "mirrors pre_tool_use" \u2014 misleading, because pre_tool_use treats allow as the implicit default while permission_request treats it as an explicit auto-approve verdict (and that asymmetry is the whole reason Result.PermissionAllowed exists separately from Result.Allowed). New comment spells out the contract. * examples/hooks.yaml now documents that the command-style hooks need jq, with install hints and a note on the graceful-degradation behaviour when jq is missing. Plus a regression test pinning the user_prompt_submit gating contract: fires exactly once on a top-level submission, never on a sub-session (SendUserMessage=false). The test caches a counter across the two cases via a tiny shared scaffold. Assisted-By: docker-agent
7d14f65 to
1149655
Compare
rumpl
approved these changes
Apr 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds the four lifecycle hook events that Claude Code, OpenCode, and
piall expose but docker-agent did not, plus a small documentation fix.Why
While auditing how docker-agent's hook system compares to peer AI coding agents, four events stood out as universally supported by competitors but missing from us. Each one unlocks a real use case that today either has to be hacked in via
pre_tool_use/on_user_inputor simply isn't possible.New hook events
user_prompt_submitRunStream, aftersession_startand before the first model call. Skipped for sub-sessions (whose kick-off message is synthesised by the runtime).additional_contextfor that turn.pre_compactsummarizeWithSourcebefore context compaction. Trigger source (manual/auto/overflow/tool_overflow) is reported inInput.Source.subagent_stoprunSubSessionForwardingandrunSubSessionCollectingcomplete — success or failure (deferred dispatch).permission_requestaskUserForConfirmationjust before the runtime would prompt the user.permission_decision: allow / deny), mirroringpre_tool_use. Returning nothing falls through to the interactive confirmation.Also fixed
post_tool_usedoc / schema / example wording: it fires on both success and failure, withtool_response.is_errordistinguishing them. The previous "after a tool completes successfully" claim was wrong.EventPermissionRequest's doc comment now spells out the asymmetry withpre_tool_use(where allow is the implicit default vs. permission_request where it's an explicit auto-approve verdict). That's whyResult.PermissionAllowedexists separately fromResult.Allowed.Files touched
pkg/config/latest/types.go— 4 new fields onHooksConfig+ validationpkg/hooks/{types,executor,config}.go— 4 newEventTypeconstants,Result.PermissionAllowed,Input.{Prompt, AgentName, ParentSessionID}, executor wiringpkg/runtime/{hooks,loop,runtime,agent_delegation,skill_runner,tool_dispatch}.go— dispatch helpers and call-site integrationagent-schema.json,examples/hooks.yaml,docs/configuration/hooks/index.md— schema, example yaml demonstrating all events, and user-facing docsTesting
pkg/hooks/contract_widening_test.gopin the wire format for every new event (block-produces-deny, allow-produces-permission-allowed, fields-reach-the-hook, …).pkg/runtime/user_prompt_submit_test.gopin the gating contract: fires-once for top-level submissions, never for sub-sessions (SendUserMessage=false).examples/hooks.yamlparses throughconfig.Loadand validates againstagent-schema.json.mise run lint→ 0 issues.go test -count=1 ./...→ 0 failures across ~150 packages.Commits
feat(hooks): add 4 new hook events to match Claude Code / OpenCode / pi— the featurerefactor(hooks): simplify the call sites added for the new events— small in-place readability cleanup (merge duplicated guards, switch onPermissionDecision, stop double-decoding tool args, take*agent.Agentdirectly instead of name + lookup)fix(hooks): address review findings on the new hook events—subagent_stopnow fires on the error path of both sub-session helpers (defer);EventPermissionRequestdoc clarification;examples/hooks.yamljq-dependency note; user_prompt_submit gating regression testBackward compatibility
HooksConfigare allomitempty; existing configs continue to parse unchanged.Summarize's public signature is unchanged; internal call-sites use a privatesummarizeWithSourceto attribute the trigger topre_compacthooks.runSubSessionForwarding's parameter list changed (string →*agent.Agent) but the function is package-private; both call-sites updated.