0073 — A self-fixable blocker gets a fix owner the same turn, and only six things are a true hold
- Status: Accepted 2026-10-25. Recorded at decision time (ADR-0004).
- Date: 2026-10-25
- Deciders: Gera, who approved both fixes on 2026-10-25 after the transcript scan below. Earlier the same day, on the 10-23 stall: "why didnt u do this when blocked, we did a full day wasted due to nothing".
- Systems:
- Driver ↔ Worker blocker handling (
local-bincanon/driver-SKILL.md§4d,canon/worker-SKILL.md§4/§5): the rule text. - blocker-trap (
local-bincanon/blocker-trap.py, linked into~/.claude/hooks/and registered byloop-doctor): the tripwire. - review-turns (
dotclaudeskills/review-turns/SKILL.md): its "say so and stop" on a down listener.
- Driver ↔ Worker blocker handling (
- Implemented by: local-bin #660 (rules), local-bin #659 (hook), dotclaude #308 (review-turns).
What was measured
A scan of 1,727 transcripts since 2026-10-18 found that about half of the moments where a session stopped short were self-fixable. The worst case: the stage smoke-test login stayed broken for 7 days and hit 8 sessions. Once a helper was asked, it was a 4-minute fix that touched only stage (the stage auth row still held a pre-10-16 password hash). The cause was instruction text. §4d(a) said "a lane that hits an external blocker does NOT fix it … and then it HOLDS", with no owner named and no attempt required. Coordinators pasted that text into briefs 103 times across 29 sessions.
The decision
- Stage, local, dev-server, test-account, CI-flake and credential-we-hold problems are ours. The coordinator assigns ONE fix owner in the same turn the blocker is reported: the first lane that reported it, or a fix helper. A worker may not report BLOCKED on such a problem without naming the fix it tried.
- Only six things are a true hold: prod data writes, money, customer-facing sends, irreversible actions, a genuine product decision, and a harness permission denial. These go to Gera (
blocked raise), not to a fix owner. - ADR-0015's one-owner rule stands. Parallel lanes still do not each fix a shared problem. The change is that assigning the owner is now same-turn and mandatory, so "hold" always means hold for someone.
- The tripwire is a hook, not a daemon. ADR-0067 deleted the Supervisor for token cost, so nothing polls.
blocker-trapgreps a helper's report as it arrives. Most reports arrive as<task-notification>prompts on UserPromptSubmit (10,811 of 10,813 last week); foreground Agent results come in on PostToolUse. On a hit, the hook injects a reminder. At Stop it blocks once if the turn neither started a helper nor named one of the six categories. It uses no model call and fails open. A quiet Stop costs one stat.
How systems relate after this
This is a new signal → rung edge in the sense of ADR-0003. A helper's report is the signal, and the coordinator's own Stop is the rung that must answer it. No system calls another. The hook reads text the coordinator already receives, and it can only add one more turn.
Open
SendMessageis not matched. It is not in the Claude Code hooks docs and no live session has proved it fires, so it was dropped as unverified (review of local-bin #659). Add it back once a live session proves it fires. Until then, a coordinator that hands the fix to an existing helper by SendMessage, instead of starting one, should name the owner in its reply; the Stop check reads only Agent calls and the six categories.- The pattern list is regex over free text. Run over last week, it fires on 1.15% of helper reports (124 reports in 21 sessions). A missed phrasing is a miss, not an error. Widen the list from
fires.logand real misses, not by guessing.