Repository navigation
[AI-1005] Durable subagent-stop via bounded POST + spool - #189
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PR Summary by QodoDurable subagent-stop: bounded POST + spool with budget-safe pre-drain Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1.
|
…klog) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AI-1005 (CLI side): durable
subagent-stopCompanion to the kcap-server fix (kcap-server#829). That PR makes the read model recover from a missing
SubagentCompleted(cascade + reaper). This PR reduces the rate at which those completions are dropped in the first place.Problem
The merged
durable-hook-spoolwork (#187) gavesession-startandsession-enddedicated bounded-POST + spool-on-transient paths, butsubagent-stopwas left out — it still fell through to the sharedPostWithRetryAsyncpath, which on failure writes stderr andreturn 1with no spool. Under hook-budget pressure / fan-out, a droppedsubagent-stopPOST permanently loses that subagent'sSubagentCompleted, leaving a stuck spinner in the UI.Fix
A dedicated
subagent-stopbranch inClaudeHookCommand.HandleCore(mirrors thesession-endblock), gated onagent_idbeing present:PostOnceAsyncclamped toHookBudget.Remaining(processStart, "subagent-stop")(5s ceiling);spool.Append(sessionId, "subagent-stop", body)and return 0;agent_id→ unchanged (falls through to the shared path).The existing cross-session drainer replays spooled entries to
/hooks/subagent-stop; server-sideSubagentCompletedids are deterministic (UUIDv5 over session+agent), so replay is idempotent — no server changes required.Pre-drain clamp (also fixed here)
subagent-stop's pre-drain (KillWatcher+InlineDrain) was capped atPreHookDrainCap(8s) while the hook ceiling is only 5s — against a hung server the drain alone could blow the budget before the bounded POST, so the spool path was never reached before Claude's kill. It is now clamped to the remaining budget.Tests
TDD; mirrors the
session_end_*tests. New:subagent_stop_on_5xx_is_spooled_and_returns_zero,subagent_stop_against_hung_server_is_spooled_within_budget(elapsed < 5s),subagent_stop_on_4xx_is_not_spooled,subagent_stop_without_agent_id_is_not_spooled,spooled_subagent_stop_is_replayed_on_next_hook. Full unit suite 1806/1806, AOT-clean (no IL2026/IL3050).🤖 Generated with Claude Code