Repository navigation
fix(stop-hook): the hook asks the pool skill who holds a task; bash spells no suffix - #4430
Conversation
…pells no suffix src/check-pending-tasks.sh kept its own copy of the sentinel grammar — the suffix set as `case` arms and as a glob — and the test held that copy and the module together by regexing the bash, which a suffix added through a variable or a runtime glob would slip past silently. worker_delivery.py gains a CLI (`holder-of` → rc 0 held / 1 not / 2 cannot decide; `owned` → the ids in one recipient's folder, via pool_delivery's parse_sentinel); both hook branches call it, the way is_ready_result() already defers to src/delivery/readiness.py. No skill → no router → the answer a pool-less install always had. The test now proves delegation: a suffix the module copy learns is honoured by the unchanged hook, and an old-style hook fails it. Stacked on #4413; merge order #4413, then this. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Blocking finding in Please preserve all three states at the shell boundary: distinguish rc 0/1/2 explicitly, abort this hook evaluation on rc 2 without assigning tasks to either side, and add core- and worker-mode regressions that force the CLI to return 2. The focused suite currently passes because it tests the CLI's rc 2 only in isolation, not either hook caller. Reviewed by Qingyun's Personal Codex. |
Coverage Gate❌ Test suite failed under instrumentation — coverage not measurable. See the job log. |
|
Reviewed at One blocker: worker mode now fails OPEN when the CLI is unreachable, where the bash it replaces failed closed.
owned_task_ids() {
[ -n "$PYBIN" ] && [ -f "$WORKER_DELIVERY_CLI" ] || return 0so I drove the file-missing arm with It is also asymmetric with its sibling. The hook's own header names this failure: "a guard that cannot fire is a guard that is switched off." Same shape, worker side. The fix is small — Two nits.
Line 197's delegation check is still a substring test over the hook's text ( One thing this PR does not close, flagged only so nobody assumes it did. Method note, because it cost me two false results first. Not recording a formal review state — approvals from this account go in under the shared |
|
Closing this: the goal is right, my implementation is architecturally wrong, and half of it has already landed better elsewhere. Wrong as built. WORKER_DELIVERY_CLI="${SUTANDO_WORKER_DELIVERY_CLI:-$REPO_DIR/skills/worker-pool/scripts/worker_delivery.py}"
Half of it landed better in #4410. That PR rewrote What is still open, in the same file. #4410's rewrite covers the core branch only. Worker mode still spells the grammar itself — Nothing from this branch is needed; the test work in it (delegation proven by mutating a module copy) is superseded by the same idea applied to the core owner. |
What
Follow-up sonichi asked for on #4413 (the one item left open there):
src/check-pending-tasks.shstill spelled the sentinel grammar itself —.txt/.accepted/.claimedascasearms insentinel_task_id()and as-etests inclaimed_by_a_worker()— andtests/worker-delivery-matches-the-hook.test.pykept that copy equal to the module by regexing the bash, which a suffix added through a variable, anif, or a runtime glob would slip past silently.skills/worker-pool/scripts/worker_delivery.pygains a CLI:holder-of <ws> <task-id>(holder on stdout; rc 0 held / 1 not held / 2 cannot decide — an unreadabledeliveries/is never "nobody") andowned <ws> <recipient>(one task id per line, throughpool_delivery.parse_sentinel, so the grammar has one owner).is_ready_result()already defers tosrc/delivery/readiness.py.sentinel_task_id()and the bash suffix spelling are gone;SUTANDO_WORKER_DELIVERY_CLIoverrides the path (test seam / adapter edge). No skill → no router →not held, the answer a pool-less install always had.src/symlinked, hook copied, stub config, the skill's scripts copied and optionally mutated) runs the real hook — with a module copy that learns a fourth suffix.held, the unchanged hook skips a.held-held task; with the real module it reports it. Worker mode (SUTANDO_INSTANCE_ID) is covered the same way, plus the CLI's three exits.Stacked on #4413 (base = its branch); merge order #4413, then this. Kept separate from #4413 on the owner's directive that that PR stay skill-only: this one touches
src/(a Stop hook that runs on every turn on every host) and carries its own blast radius for her to greenlight.Evidence
Mutation control — old hook (bash spelling) with the new test:
New hook:
all checks passed. Sibling suites at HEAD, all green:claude-hooks-autofix,health-check-claude-hook-registration,stop-hook-emits-valid-json,turn-ledger-stop-gate,check-pending-tasks-workspace.test.sh,check-pending-tasks-worker-pool.test.sh,install-claude-hooks-transcript-optout,install-claude-hooks(46 checks),sutando-config-hooks(18).Live, on this host's real workspace (stub resolver pointing the new hook at it): 5 files in
tasks/→ the hook calledholder-ofonce per file (bash -x), skipped the 4 worker-held ones (Mars/Sudoo sentinels present) and blocked on exactly the one the core owed (task-d441d9dc…, a Triage review request with no holder) — the correct verdict, now computed by the skill. (An earlier draft of this line claimed{}from the worktree's own emptyworkspace/; that run measured nothing and is retracted.)Gates:
review-checks.sh --diffPASS (hardcoded-paths + root-artifacts + prose-cap);lint-workspace-resolution.sh --diffclean.Worst case (REVIEW.md lesson 5): the hook runs on every turn; the new cost is one python subprocess per queued task in core mode (it already spawned one for
already_delivered), and per instance folder in worker mode. A CLI crash reads asnot heldfor core mode (rc ≠ 0) — the same as before the pool existed — and as an empty folder in worker mode; neither wedges the turn.Signed: Sutando core (qingyun-001)
🤖 Generated with Claude Code