# NousResearch/hermes-agent · unlink_tasks missing recompute_ready call fix

## Open now

- Nothing open on GitHub.

These issues and PRs concern a bug where unlink_tasks does not trigger recompute_ready, potentially leaving tasks stuck. Multiple fix attempts and a salvage PR address the same missing call.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #22669. Verify it addresses your case.
- **Recommended:** read PR #22669 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-22669`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #22459: \[Bug\]: unlink_tasks does not trigger recompute_ready — tasks stuck in todo without dispatcher

## Prior & competing PRs

- **#22454: fix(kanban): trigger recompute_ready after unlink_tasks**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `0c22434f0`, `#22459`
- **#22669: fix(kanban): call recompute_ready after unlink_tasks (salvage #22646)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/kanban_db.py`, `scripts/release.py`, `tests/hermes_cli/test_kanban_db.py`
  - fix hunks:
  - `hermes_cli/kanban_db.py`

    ```diff
    @@ -1504,7 +1504,14 @@ def unlink_tasks(conn: sqlite3.Connection, parent_id: str, child_id: str) -> boo
                     conn, child_id, "unlinked",
                     {"parent": parent_id, "child": child_id},
                 )
    -        return cur.rowcount > 0
    +        removed = cur.rowcount > 0
    +    if removed:
    +        # Dependency edge removed — re-evaluate promotion eligibility for the
    +        # child immediately.  Matches the contract of complete_task and
    +        # unblock_task; without this the child stays stuck in todo until the
    +        # next dispatcher tick or a manual `hermes kanban recompute` (issue #22459).
    +        recompute_ready(conn)
    +    return removed
     
     
     def parent_ids(conn: sqlite3.Connection, task_id: str) -> list[str]:
    ```
  - `scripts/release.py`

    ```diff
    @@ -64,6 +64,7 @@
         "[redacted:email]": "liquidchen",
         "[redacted:email]": "subtract0",
         "[redacted:email]": "qWaitCrypto",
    +    "[redacted:email]": "wesleysimplicio",
         "[redacted:email]": "uzunkuyruk",
         "[redacted:email]": "Zhekinmaksim",
         "[redacted:email]": "KvnGz",
    ```
  - `tests/hermes_cli/test_kanban_db.py`

    ```diff
    @@ -966,3 +966,37 @@ def wal_blocking_connect(*args, **kwargs):
         tasks = kb.list_tasks(conn)
         assert any(row.id == t for row in tasks)
         conn.close()
    +
    +
    +def test_unlink_tasks_triggers_recompute_ready(kanban_home):
    +    """Regression test for issue #22459.
    +
    +    Removing a dependency via unlink_tasks must immediately promote the child
    +    to ready when all remaining parents are done — same contract as
    +    complete_task and unblock_task.
    +
    +    Before the fix, child stayed 'todo' indefinitely after unlink; only the
    +    next dispatcher tick or a manual 'hermes kanban recompute' would promote it.
    +    """
    +    with kb.connect() as conn:
    +        # A is done.
    +        a = kb.create_task(conn, title="parent-done")
    +        kb.complete_task(conn, a)
    +
    +        # C is running (not done) — blocks child B.
    +        c = kb.create_task(conn, title="parent-running")
    +        kb.claim_task(conn, c, claimer="worker:1")
    +
    +        # B depends on both A (done) and C (running) → stays todo.
    +        b = kb.create_task(conn, title="child", parents=[a, c])
    +        assert kb.get_task(conn, b).status == "todo"
    +
    +        # Remove the blocking dependency C → B.
    +        removed = kb.unlink_tasks(conn, c, b)
    +        assert removed is True
    +
    +        # B's only remaining parent is A (done) → must be ready immediately.
    +        assert kb.get_task(conn, b).status == "ready", (
    +            "child should promote to ready immediately after unlink_tasks "
    +            "removes its last blocking dependency"
    +        )
    ```
- **#22646: fix(kanban): call recompute_ready after unlink_tasks removes a dependency**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#22669`

## Fix status

- Merged to main in PR #22669 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-09`.
