{"repo":"NousResearch/hermes-agent","public_id":"22459","display_label":"unlink_tasks missing recompute_ready call fix","summary":"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.","subsystem":null,"counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":22669,"merged_at":"2026-05-09T18:06:21+00:00"},"resolution":{"state":"resolved","by":"pr-22669"},"freshness":{"state":"verified","last_verified_at":"2026-05-09T18:06:21+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":21708,"pr_number":22669,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-05-09T18:06:21+00:00"},{"issue_number":22459,"pr_number":22669,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-09T18:06:21+00:00"}],"members":[{"kind":"issue","number":22459,"title":"[Bug]: unlink_tasks does not trigger recompute_ready — tasks stuck in todo without dispatcher","url":null,"state":"closed"},{"kind":"pr","number":22454,"title":"fix(kanban): trigger recompute_ready after unlink_tasks","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"0c22434f0","number":0},{"kind":"pr","value":"#22459","number":22459}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":22646,"title":"fix(kanban): call recompute_ready after unlink_tasks removes a dependency","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#22669","number":22669}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":22669,"title":"fix(kanban): call recompute_ready after unlink_tasks (salvage #22646)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/kanban_db.py","scripts/release.py","tests/hermes_cli/test_kanban_db.py"],"fix_hunks":[{"file":"hermes_cli/kanban_db.py","hunks":["@@ -1504,7 +1504,14 @@ def unlink_tasks(conn: sqlite3.Connection, parent_id: str, child_id: str) -> boo\n                 conn, child_id, \"unlinked\",\n                 {\"parent\": parent_id, \"child\": child_id},\n             )\n-        return cur.rowcount > 0\n+        removed = cur.rowcount > 0\n+    if removed:\n+        # Dependency edge removed — re-evaluate promotion eligibility for the\n+        # child immediately.  Matches the contract of complete_task and\n+        # unblock_task; without this the child stays stuck in todo until the\n+        # next dispatcher tick or a manual `hermes kanban recompute` (issue #22459).\n+        recompute_ready(conn)\n+    return removed\n \n \n def parent_ids(conn: sqlite3.Connection, task_id: str) -> list[str]:"]},{"file":"scripts/release.py","hunks":["@@ -64,6 +64,7 @@\n     \"[redacted:email]\": \"liquidchen\",\n     \"[redacted:email]\": \"subtract0\",\n     \"[redacted:email]\": \"qWaitCrypto\",\n+    \"[redacted:email]\": \"wesleysimplicio\",\n     \"[redacted:email]\": \"uzunkuyruk\",\n     \"[redacted:email]\": \"Zhekinmaksim\",\n     \"[redacted:email]\": \"KvnGz\","]},{"file":"tests/hermes_cli/test_kanban_db.py","hunks":["@@ -966,3 +966,37 @@ def wal_blocking_connect(*args, **kwargs):\n     tasks = kb.list_tasks(conn)\n     assert any(row.id == t for row in tasks)\n     conn.close()\n+\n+\n+def test_unlink_tasks_triggers_recompute_ready(kanban_home):\n+    \"\"\"Regression test for issue #22459.\n+\n+    Removing a dependency via unlink_tasks must immediately promote the child\n+    to ready when all remaining parents are done — same contract as\n+    complete_task and unblock_task.\n+\n+    Before the fix, child stayed 'todo' indefinitely after unlink; only the\n+    next dispatcher tick or a manual 'hermes kanban recompute' would promote it.\n+    \"\"\"\n+    with kb.connect() as conn:\n+        # A is done.\n+        a = kb.create_task(conn, title=\"parent-done\")\n+        kb.complete_task(conn, a)\n+\n+        # C is running (not done) — blocks child B.\n+        c = kb.create_task(conn, title=\"parent-running\")\n+        kb.claim_task(conn, c, claimer=\"worker:1\")\n+\n+        # B depends on both A (done) and C (running) → stays todo.\n+        b = kb.create_task(conn, title=\"child\", parents=[a, c])\n+        assert kb.get_task(conn, b).status == \"todo\"\n+\n+        # Remove the blocking dependency C → B.\n+        removed = kb.unlink_tasks(conn, c, b)\n+        assert removed is True\n+\n+        # B's only remaining parent is A (done) → must be ready immediately.\n+        assert kb.get_task(conn, b).status == \"ready\", (\n+            \"child should promote to ready immediately after unlink_tasks \"\n+            \"removes its last blocking dependency\"\n+        )\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}