Skip to content

fix(kanban): hint at the owning board when a verb fails on a wrong-board id (#65101) - #65128

Open
nolanchic wants to merge 2 commits into
NousResearch:mainfrom
nolanchic:fix/kanban-cross-board-error
Open

nolanchic wants to merge 2 commits into
NousResearch:mainfrom
nolanchic:fix/kanban-cross-board-error

Conversation

@nolanchic

Copy link
Copy Markdown

What does this PR do?

When a kanban verb (block / archive / complete) fails because the task id is real but lives on a different board, the operator gets an opaque error and no idea a boards switch would fix it. The id is known and the card is in a valid state — it is simply on the wrong board. Per #65101, this caused an operator to retry the same failing command 8 times.

Today the operator sees one of two equally-unhelpful messages depending on the verb path:

  • kanban: unknown task t_4dff09c7 — raised before the verb's state check (e.g. block <id> <reason> calls add_comment first, which throws on the missing id), surfaced through kanban_command's shared except (ValueError, RuntimeError) branch.
  • cannot block t_4dff09c7 — printed when the verb's task op itself returns False (e.g. block <id> with no reason reaches block_task → False).

Neither names the current board, neither mentions that the task exists elsewhere, and neither suggests hermes kanban boards switch <slug>.

This PR attaches a one-line hint to both failure shapes:

cannot block t_4dff09c7 (unknown id or not in running/ready)
  → 't_4dff09c7' is on board 'default', not on the active board 'cqgambit'. Switch with: hermes kanban boards switch default

The hint is shown only when the id is actually found on another board. When the task is on the active board (a genuine wrong-state failure) or absent from all boards, no hint is shown — so we never chase the operator to a board where the task still isn't.

The cross-board scan reuses the existing list_boards + connect(board=...) pattern; a get_task check on the active connection gates the scan so the common wrong-state path stays cheap.

Related Issue

Fixes #65101

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_cli/kanban.py
    • New _locate_task_on_other_board() / _cross_board_hint(): scan other boards for a task id, return a switch hint (or None). Reuses list_boards + connect(board=...).
    • New _task_ids_from_unknown_task_message(): recover t_<hex> ids from the unknown task … / unknown task(s): … error strings so the hint can fire in the shared exception branch.
    • kanban_command shared except (ValueError, RuntimeError): when the message is unknown task …, parse the id(s) and attach the cross-board hint. This covers every verb that touches a task before its own state check (block-with-reason via add_comment, etc.).
    • _cmd_block / _cmd_archive / _cmd_complete False branches: attach the hint after the existing cannot <verb> {id} line. Also clarified the inline parenthetical for block/archive (previously bare) to match complete's style.

How to Test

pytest tests/hermes_cli/test_kanban_cross_board_error.py -q — 9 tests, all passing. Covers:

  1. block/archive/complete of a task on another board → hint with owning board + switch command.
  2. block with no reason reaching block_task → False → cannot block + hint.
  3. Wrong-state failure (task on active board, e.g. blocking a done task) → no hint.
  4. Genuinely unknown id (not on any board) → no switch hint.
  5. Unit tests for _cross_board_hint and the message parser (singular, plural, dedup, non-id tokens ignored).

Manual repro (the exact scenario from the issue):

hermes kanban boards switch cqgambit          # active board is cqgambit
hermes kanban block t_4dff09c7 "test" --kind needs_input   # card is on `default`
# Before:  kanban: unknown task t_4dff09c7
# After:   kanban: unknown task t_4dff09c7
#          → 't_4dff09c7' is on board 'default', not on the active board 'cqgambit'. Switch with: hermes kanban boards switch default

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've added tests for my changes (required for bug fixes)
  • I've run the new test file and all tests pass
  • I've tested on my platform: macOS 15

Documentation & Housekeeping

  • N/A — no config keys, tool schemas, or architecture changes

@alt-glitch alt-glitch added type/bug Something isn't working comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have labels Jul 15, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for covering both the exception and false-return failure shapes; the current-main premise is valid (hermes_cli/kanban.py:1949-1957, 1866-1908, 2071-2096).

Problems

  • hermes_cli/kanban.py:1908 cannot scan another board when HERMES_KANBAN_DB is set. Current main resolves that env override before board= (hermes_cli/kanban_db.py:530-538), and spawned workers always receive it (hermes_cli/kanban_db.py:8154). The scan therefore reconnects to the active DB and finds no owning board.
  • The tests set only HERMES_KANBAN_BOARD (tests/hermes_cli/test_kanban_cross_board_error.py:41-117), so they miss the dispatcher environment and the documented worker isolation contract (website/docs/user-guide/features/kanban.md:80-89).

Suggested changes

  • Preserve DB-pinned worker isolation explicitly, then add a regression test for that environment and the intended output.

Automated hermes-sweeper review.

Comment thread hermes_cli/kanban.py Outdated
other = meta.get("slug")
if not other or other == active:
continue
other_conn = kb.connect(board=other)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HERMES_KANBAN_DB overrides board= in current kanban_db_path() (hermes_cli/kanban_db.py:530-538). Dispatcher-spawned workers set that pin, so this reconnects to the active DB instead of other and the hint is never found. Please preserve the worker isolation contract explicitly and add coverage with the DB pin set.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 16, 2026
nolanchic added a commit to nolanchic/hermes-agent that referenced this pull request Jul 16, 2026
…-board hint

The wrong-board hint scan called connect(board=other), but
kanban_db_path() honours HERMES_KANBAN_DB first — and the dispatcher
always pins that into worker env (kanban_db.py worker handoff). So under
a real dispatch the scan reopened the active board's DB for every
'other' board, never found the owning board, and the hint stayed silent
(NousResearch#65101 regressed under dispatch, NousResearch#65128).

- Add board_db_path(board): a pure path resolver that ignores
  HERMES_KANBAN_DB (and has no active-board fallback), refactored out
  of kanban_db_path so both share _board_db_path_for_slug.
- The scan now connects via connect(db_path=board_db_path(other)), the
  explicit-db_path branch of connect() which bypasses the env override.
  Worker isolation is untouched — business connections still resolve via
  the pinned env path.
- Regression test pins the dispatcher scenario: with
  HERMES_KANBAN_DB pinned to the active board, the hint still names the
  owning board. RED-verified (fails on the pre-fix connect(board=other)).
@nolanchic

Copy link
Copy Markdown
Author

Thanks @teknium1 — root-caused and fixed in f421662f.

Root cause: the scan called connect(board=other), but kanban_db_path() honours HERMES_KANBAN_DB first and the dispatcher always pins that into worker env. So under a real dispatch every connect(board=other) reopened the active board's DB, the owning board was never found, and the hint stayed silent — exactly the worker-isolation contract you pointed at.

Fix:

  • Added board_db_path(board) — a pure path resolver that ignores HERMES_KANBAN_DB (no active-board fallback either), refactored out of kanban_db_path so both share _board_db_path_for_slug. Worker isolation is untouched: business connections still resolve via the pinned env path.
  • The scan now connects via connect(db_path=board_db_path(other)) — the explicit-db_path branch of connect() bypasses the env override.

Regression test (test_cross_board_hint_with_db_pinned_to_active): with HERMES_KANBAN_DB pinned to the active board exactly as the dispatcher does, the hint still names the owning board. RED-verified — it fails on the pre-fix connect(board=other) with assert hint is not None.

One subtlety the test had to get right: the task must be created on other before the DB pin is set, otherwise _task_on_board's own connect(board=other) gets hijacked too. The dispatcher scenario doesn't hit this (the task pre-exists on other), but it's worth noting the pin is sticky for all connect(board=...) callers, not just the scan.

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

One PR directly addresses issue #65101. #65128 adds owning-board and switch-command hints to both exception-based and false-return failures for block, archive, and complete, while suppressing those hints for genuine wrong-state or unknown-ID failures.

Related pull requests

  • fix(kanban): hint at the owning board when a verb fails on a wrong-board id (#65101) #65128 best fix — (+357/-5) — n/a: The diff scans sibling board databases for failed task IDs, reports the owning board with the exact switch command, and adds focused regression coverage. The contributor’s keep_open review identified that HERMES_KANBAN_DB prevented cross-board scans in dispatched workers; the current diff explicitly bypasses that pin through board_db_path(other), and test_cross_board_hint_with_db_pinned_to_active covers the reported environment.

Suggested consolidation

Keep #65128 open with a salvage path: retain its cross-board lookup, actionable hints across both failure shapes, and pinned-worker regression test. It is the recorded best fix and the only PR in this complex; the current diff explicitly addresses the contributor’s keep_open concern, so there is no competing PR to close as a duplicate.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I65101(["issue #65101 (open)"])
    P65128["PR #65128 (open)"]
    P65128 -->|best fix| I65101
    class I65101 open
    class P65128 open
    class P65128 best
    class P65128 target
    click I65101 "https://github.com/NousResearch/hermes-agent/issues/65101"
    click P65128 "https://github.com/NousResearch/hermes-agent/pull/65128"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 18 kB of PR diffs, 6 kB of issue/PR text, 5 kB of discussion (3 comments), 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

nolanchic and others added 2 commits September 14, 2026 17:44
…ard id (NousResearch#65101)

When a kanban verb (block/archive/complete) fails because the task id is real
but lives on a different board, the operator sees an opaque 'unknown task {id}'
exception or a bare 'cannot <verb> {id}' line. The id is known and the card is
in a valid state — it is simply on the wrong board — yet nothing in the error
says so or suggests the 'hermes kanban boards switch <slug>' fix. This caused
operators to retry the same failing command many times (NousResearch#65101).

Two failure shapes are covered:

* 'unknown task {id}' raised before a verb's state check (e.g. block with a
  reason calls add_comment first). Caught in kanban_command's shared
  except (ValueError, RuntimeError) branch that all verbs funnel through.
* 'cannot <verb> {id}' printed when a verb's task op returns False (e.g. block
  without a reason reaches block_task -> False).

Both now attach a one-line hint naming the owning board and the exact switch
command, but only when the id is actually found on another board. No hint when
the task is on the active board (a genuine wrong-state failure) or absent from
all boards (so we never chase the operator elsewhere).

The cross-board scan reuses the existing list_boards + connect(board=...)
pattern; a get_task check on the active connection gates the scan so the
common wrong-state case stays cheap.
…-board hint

The wrong-board hint scan called connect(board=other), but
kanban_db_path() honours HERMES_KANBAN_DB first — and the dispatcher
always pins that into worker env (kanban_db.py worker handoff). So under
a real dispatch the scan reopened the active board's DB for every
'other' board, never found the owning board, and the hint stayed silent
(NousResearch#65101 regressed under dispatch, NousResearch#65128).

- Add board_db_path(board): a pure path resolver that ignores
  HERMES_KANBAN_DB (and has no active-board fallback), refactored out
  of kanban_db_path so both share _board_db_path_for_slug.
- The scan now connects via connect(db_path=board_db_path(other)), the
  explicit-db_path branch of connect() which bypasses the env override.
  Worker isolation is untouched — business connections still resolve via
  the pinned env path.
- Regression test pins the dispatcher scenario: with
  HERMES_KANBAN_DB pinned to the active board, the hint still names the
  owning board. RED-verified (fails on the pre-fix connect(board=other)).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kanban block prints misleading error when card is on a different board

4 participants