Skip to content

fix(kanban): worker-initiated block must not be auto-promoted (#28712) - #28994

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-1f3d32a0
May 20, 2026
Merged

fix(kanban): worker-initiated block must not be auto-promoted (#28712)#28994
teknium1 merged 2 commits into
mainfrom
hermes/hermes-1f3d32a0

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Salvage of #28726 — worker / operator-initiated kanban_block() is now sticky. recompute_ready skips tasks whose latest block-related event in task_events is "blocked" while continuing to auto-promote circuit-breaker blocks (which emit "gave_up", not "blocked"). Fixes #28712.

Why salvage instead of merge #28726

#28726 also included a schema-init ordering fix for idx_tasks_session_id on legacy DBs. That half is already on main via #28754 (Michael Nguyen) and #28781 (kshitijk4poor), which is why the original PR was showing CONFLICTING. This salvage cherry-picks only the sticky-block half — xxxigm's authorship is preserved per-commit.

Changes

  • hermes_cli/kanban_db.py — new _has_sticky_block(conn, task_id) helper; recompute_ready() skips tasks whose latest blocked/unblocked event is blocked. +55 / −1 lines.
  • tests/hermes_cli/test_kanban_blocked_sticky.py — 6 regression tests covering both legs of the loop, the circuit-breaker preservation, the unblock exit, and the full protocol-violation reproduction. The original PR's 7th test (legacy-DB init recovery) was dropped during salvage — that contract is already covered by test_kanban_db.py::test_connect_migrates_legacy_db_before_optional_column_indexes on main.

Validation

Result
tests/hermes_cli/test_kanban_blocked_sticky.py 6 passed
tests/hermes_cli/test_kanban_db.py 158 passed
tests/hermes_cli/test_kanban_{cli,core_functionality,notify,diagnostics}.py + tests/tools/test_kanban_tools.py 350 passed
E2E (real SQLite DB, real kanban_block / recompute_ready / unblock_task) sticky-block holds 5 ticks; unblock_task clears it; circuit-breaker blocks (gave_up event only) still auto-recover; full protocol-violation loop confirmed broken

Credit

xxxigm added 2 commits May 19, 2026 17:13
When a worker calls ``kanban_block(reason="review-required: ...")`` to
hand a task off for human review, the dispatcher's ``recompute_ready``
was treating the resulting ``blocked`` status as eligible for
auto-promotion — exactly the same as a circuit-breaker block.  On the
next tick the task flipped back to ``ready``, a fresh worker spawned,
found nothing to do (work already applied, review-required comment
already posted), exited cleanly, got recorded as ``protocol_violation``
→ ``gave_up`` → ``blocked``, and the dispatcher promoted again.
Infinite loop until manual ``hermes kanban reclaim`` + ``kanban block``.

Add ``_has_sticky_block`` which distinguishes the two block sources
using the cheapest available signal: the most recent
``"blocked"``/``"unblocked"`` event in ``task_events``.

* Worker / operator ``kanban_block`` emits ``"blocked"`` →
  ``_has_sticky_block`` returns True → ``recompute_ready`` skips the
  task entirely.  ``unblock_task`` emits ``"unblocked"`` which flips
  the predicate back, so the only legitimate exit is the documented
  human-in-the-loop path.
* Circuit-breaker ``_record_task_failure`` emits ``"gave_up"`` (not
  ``"blocked"``) → predicate stays False → original
  parent-completion-recovery semantics from #40c1decb3 are preserved.
* Tasks blocked purely by direct DB manipulation also recover, since
  they have no ``"blocked"`` event row at all — matches the existing
  ``test_recompute_ready_promotes_blocked_with_done_parents`` fixture
  behaviour.
…28712)

Six regression tests pinning the dispatcher contract that was broken
in #28712:

* test_worker_block_is_not_auto_promoted_by_recompute_ready —
  kanban_block survives five back-to-back ticks (compressed dispatcher
  loop).
* test_worker_block_on_child_with_done_parents_is_still_sticky —
  the parent-completion code path was the worst false-positive; even
  when every parent is done, an explicit worker block stays blocked.
* test_circuit_breaker_block_still_auto_promotes — preserves the
  pre-#28712 recovery semantics for circuit-breaker blocks (direct
  UPDATE + no "blocked" event).
* test_gave_up_event_alone_does_not_make_block_sticky — explicit
  guard so the gave_up event is never accidentally treated as
  sticky; covers the second leg of the protocol_violation loop.
* test_unblock_clears_sticky_state_and_lets_block_recover — only
  unblock_task resolves the sticky state; subsequent circuit-breaker
  blocks recover normally.
* test_protocol_violation_loop_is_broken — full bug-shaped
  reproduction: block → tick → (would-be) crash + gave_up → next tick
  still blocked.  Without the fix this would loop indefinitely.

The seventh test from the original PR (legacy-DB init recovery) was
dropped during salvage — the schema-init half of #28712 is already
fixed on main by #28754 and #28781, and the contract is covered by
test_kanban_db.py::test_connect_migrates_legacy_db_before_optional_column_indexes.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-1f3d32a0 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9003 on HEAD, 8985 on base (🆕 +18)

🆕 New issues (5):

Rule Count
unresolved-attribute 4
unresolved-import 1
First entries
tests/hermes_cli/test_kanban_blocked_sticky.py:229: [unresolved-attribute] unresolved-attribute: Attribute `current_run_id` is not defined on `None` in union `Task | None`
tests/hermes_cli/test_kanban_blocked_sticky.py:132: [unresolved-attribute] unresolved-attribute: Attribute `last_failure_error` is not defined on `None` in union `Task | None`
tests/hermes_cli/test_kanban_blocked_sticky.py:131: [unresolved-attribute] unresolved-attribute: Attribute `consecutive_failures` is not defined on `None` in union `Task | None`
tests/hermes_cli/test_kanban_blocked_sticky.py:35: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_kanban_blocked_sticky.py:260: [unresolved-attribute] unresolved-attribute: Attribute `status` is not defined on `None` in union `Task | None`

✅ Fixed issues: none

Unchanged: 4736 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1
teknium1 merged commit d35f893 into main May 20, 2026
20 of 21 checks passed
@teknium1
teknium1 deleted the hermes/hermes-1f3d32a0 branch May 20, 2026 00:26
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard labels May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kanban: dispatcher auto-promotes blocked task → respawn worker → protocol_violation loop

3 participants