🐛 fix(kanban): recognize a no-PR edit-in-place clean exit as provably-done - #75
Conversation
…-done An edit-in-place / no-PR card (workspace scratch/dir, or a ~/.hermes workdir) completes its lane by exiting rc=0 WITHOUT a terminal kanban verb — by design, since done ≡ merged only applies to PR-backed cards and there is no PR to open. detect_crashed_workers then misread that verb-less rc=0 exit as a protocol_violation, tripped failure_limit=1, and emitted a false gave_up, stranding a fully-completed card in blocked for a human to hand-reconcile. Root cause: _lane_work_provably_done — the helper both the clean_exit_after_done carve-out and check_respawn_guard trust — accepted exactly two proofs (a completed-run row in the success window, and a PR URL in a recent comment). A no-PR edit-in-place clean exit satisfies neither: it opens no PR (fails proof 2), and the verb-less exit is precisely why no outcome='completed' run row was written (fails proof 1). So the carve-out never fired for exactly the card shape that legitimately exits verb-less. Add a third proof, proof-gated and scoped to the no-PR shape via the shared _card_requires_pr predicate (not a new ad-hoc definition): for a card _card_requires_pr classifies as NOT PR-requiring, a durable self-verification / lane-done handoff comment within the reused _RESPAWN_GUARD_SUCCESS_WINDOW (matched by a conservative line-anchored _LANE_DONE_HANDOFF_RE) reads as landed-work proof. Absence of proof keeps the strict protocol-violation behavior — a genuinely-incomplete quiet exit on a no-PR card still counts, so this cannot mask real breakage. The existing two proofs are unchanged and a PR-requiring worktree card is still held to a real PR/completed-run artifact (PR-backed behavior unchanged). No new config, no new schema, no user-facing env var. Adds 3 behavior-contract tests (real path, temp HERMES_HOME): no-PR edit-in-place done+handoff → benign no-op surfaced via the _last_clean_exit_after_done side-channel; no-PR edit-in-place with no landed-work proof → still protocol_violation/gave_up; PR-requiring card with a handoff comment but no PR → still protocol_violation.
|
The third proof lands exactly where the root-cause analysis says it should. The handoff regex is anchored at line start with MULTILINE, so a progress note like "looking into this" won't be mistaken for a completion signpost. That keeps the carve-out proof-gated, and the without-proof test confirms a genuinely quiet exit still trips the protocol violation. Verified against head No changes needed. Ready to merge. |
…-done (#75) An edit-in-place / no-PR card (workspace scratch/dir, or a ~/.hermes workdir) completes its lane by exiting rc=0 WITHOUT a terminal kanban verb — by design, since done ≡ merged only applies to PR-backed cards and there is no PR to open. detect_crashed_workers then misread that verb-less rc=0 exit as a protocol_violation, tripped failure_limit=1, and emitted a false gave_up, stranding a fully-completed card in blocked for a human to hand-reconcile. Root cause: _lane_work_provably_done — the helper both the clean_exit_after_done carve-out and check_respawn_guard trust — accepted exactly two proofs (a completed-run row in the success window, and a PR URL in a recent comment). A no-PR edit-in-place clean exit satisfies neither: it opens no PR (fails proof 2), and the verb-less exit is precisely why no outcome='completed' run row was written (fails proof 1). So the carve-out never fired for exactly the card shape that legitimately exits verb-less. Add a third proof, proof-gated and scoped to the no-PR shape via the shared _card_requires_pr predicate (not a new ad-hoc definition): for a card _card_requires_pr classifies as NOT PR-requiring, a durable self-verification / lane-done handoff comment within the reused _RESPAWN_GUARD_SUCCESS_WINDOW (matched by a conservative line-anchored _LANE_DONE_HANDOFF_RE) reads as landed-work proof. Absence of proof keeps the strict protocol-violation behavior — a genuinely-incomplete quiet exit on a no-PR card still counts, so this cannot mask real breakage. The existing two proofs are unchanged and a PR-requiring worktree card is still held to a real PR/completed-run artifact (PR-backed behavior unchanged). No new config, no new schema, no user-facing env var. Adds 3 behavior-contract tests (real path, temp HERMES_HOME): no-PR edit-in-place done+handoff → benign no-op surfaced via the _last_clean_exit_after_done side-channel; no-PR edit-in-place with no landed-work proof → still protocol_violation/gave_up; PR-requiring card with a handoff comment but no PR → still protocol_violation. (cherry picked from commit 4a17268)
Why
An edit-in-place / no-PR card (workspace
scratch/dir, or a~/.hermesworkdir) completes its lane by exiting cleanly (rc=0) WITHOUT a terminal kanban
verb — by design, since
done ≡ mergedonly applies to PR-backed cards andthere is no PR to open.
detect_crashed_workers(hermes_cli/kanban_db.py)then misread that verb-less rc=0 exit as a
protocol_violation, trippedfailure_limit=1, and emitted a falsegave_up— stranding a fully-completedcard in
blockedand forcing a hand-reconcile.Root cause
_lane_work_provably_done— the same helper bothdetect_crashed_workers'clean_exit_after_donecarve-out andcheck_respawn_guardtrust — acceptedexactly two proofs:
task_runsrow withoutcome='completed'in the success window, andA no-PR edit-in-place clean exit satisfies neither: it opens no PR (fails
proof 2), and a verb-less clean exit is precisely why no
outcome='completed'run row was written (fails proof 1). So the carve-out never fired for exactly
the card shape that legitimately exits verb-less → guaranteed false
protocol_violation.What
Add a third proof to
_lane_work_provably_done, proof-gated and scoped tothe no-PR shape via the shared
_card_requires_pr(workspace_kind, workspace_path)predicate (the same predicate the required-artifact completionguard uses — not a new ad-hoc definition):
_card_requires_prclassifies as NOT PR-requiring, a durableself-verification / lane-done handoff comment within the reused
_RESPAWN_GUARD_SUCCESS_WINDOW(matched by a conservative, line-anchored_LANE_DONE_HANDOFF_RE) reads as landed-work proof.genuinely-incomplete quiet exit on a no-PR card still counts, so the carve-out
cannot mask real breakage.
still held to a real PR/completed-run artifact — PR-backed behavior is
byte-for-byte unchanged (the handoff-comment match never leaks into it).
No new config, no new schema, no user-facing env var — reuses the existing
window constant and the shared shape predicate.
Done when
released as a benign
clean_exit_after_doneno-op (surfaced via the existing_last_clean_exit_after_doneside-channel), not counted as a failure and notemitting
gave_up/crashed. ✅the protocol-violation breaker (proof-gated regression guard). ✅
behave exactly as before. ✅
Tests
3 behavior-contract tests added to
tests/hermes_cli/test_kanban_db.py(realkanban_db, tempHERMES_HOME, no mocks):_last_clean_exit_after_doneside-channel;protocol_violation/gave_up;protocol_violation(carve-out does not leak into PR-backed cards).Full
tests/hermes_cli/test_kanban_db.py(274) green, ruff clean, 0regressions. Carries the
PATCHES.mdmanifest row (fork supply-chain).Base:
cwest/integration.