Skip to content

feat(kanban): diagnose worktree tasks with no repo to check out in - #103120

Open
lucascmg-vx wants to merge 1 commit into
NousResearch:mainfrom
lucascmg-vx:feat/kanban-worktree-checkout-root-diagnostic
Open

lucascmg-vx wants to merge 1 commit into
NousResearch:mainfrom
lucascmg-vx:feat/kanban-worktree-checkout-root-diagnostic

Conversation

@lucascmg-vx

Copy link
Copy Markdown

What does this PR do?

A workspace_kind=worktree task resolves against its own workspace_path or the board's default_workdir. With neither, _resolve_worktree_workspace raises rather than guess a repo:

task {id} has workspace_kind=worktree but no workspace_path, and board {slug!r}
has no default_workdir set.

That is the right call — the docstring says it fails loudly "rather than guess" — but it raises at dispatch. The task burns its retries and the circuit breaker parks it as blocked behind a message that reads like an infrastructure fault instead of "this task was created with nowhere to run".

Multi-repo boards leave default_workdir unset on purpose, because a single fallback root would silently check work out into the wrong repo. On those boards this raise is the only way the mistake surfaces, and it surfaces after the retries are gone.

This adds worktree_without_checkout_root, reporting the same condition while the task is still queued — error when it is merely mis-created, critical once it is already failing.

Why the rule keys on the resolver, not on project_id. A project is what usually supplies the path: create_task turns a project link into <repo>/.worktrees/<id>. But it does that only at creation, and only when the project has a primary repo — resolution never consults project_id again. A row that has a link and no path (project without a primary repo, or a direct UPDATE) is equally undispatchable, so the rule follows _resolve_worktree_workspace and uses project_id only to choose the remedy text.

Why it does not guess the repo. Inheriting the parent task's project looks obvious. On a 24-task sample of exactly this failure it would have misdirected 4 (17%), every one from a direct parent, because cross-repo hand-offs (a spec task in one repo spawning the follow-up that edits another) are normal rather than exceptional. A silent checkout into the wrong repo is worse than a loud stall — the same reasoning the resolver already applies. Choosing the repo stays with the operator.

Related Issue

No existing issue — I searched open and closed issues and PRs for worktree, missing workdir, worktree_without_project and found none. Happy to open one if you'd prefer it tracked.

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

Changes Made

  • hermes_cli/kanban_diagnostics.py — _rule_worktree_without_checkout_root + registration in _RULES; new public helper read_board_default_workdir().
  • hermes_cli/kanban.py — pass board_default_workdir at the two compute_task_diagnostics call sites (show, diag).
  • plugins/kanban/dashboard/plugin_api.py — thread the requested board into _compute_task_diagnostics so a multi-board process judges each task against its board rather than whichever board is current.
  • tests/hermes_cli/test_kanban_diagnostics.py — 11 tests.

DIAGNOSTIC_KINDS is intentionally not touched: per COMPAT_MANIFEST.md it is a restored-def inside the revert-scheduled PLUGIN-COMPAT block, and nothing in-tree may depend on those pointers. python scripts/check_compat_pointers.py passes.

How to Test

  1. On a board with no default_workdir, create a worktree task with no project:
    hermes kanban create "demo" --workspace worktree
    hermes kanban diag
    It reports worktree_without_checkout_root at error before the task is ever dispatched. Let it fail once and it escalates to critical.
  2. Set a default_workdir on the board (or give the task an explicit --workspace worktree:<repo>) and the diagnostic clears.
  3. Tests:
    pytest tests/hermes_cli/test_kanban_diagnostics.py -q   # 16 passed
    python scripts/check_compat_pointers.py

test_worktree_without_checkout_root_matches_the_resolver builds a real task, asserts resolve_workspace raises, and asserts the rule fires on the same row — so the rule stays pinned to the code it predicts rather than to a hand-built fixture.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this feature
  • I've run the tests — see note below
  • I've added tests for my changes
  • I've tested on my platform: Ubuntu 24.04 (aarch64), Python 3.11.15

Note on the suite: pytest tests/hermes_cli/ -k "kanban or dashboard or diagnostic or worktree or workspace" gives 57 failed / 759 passed on this branch and 57 failed / 748 passed on pristine main — same failures, +11 passes (exactly the new tests). Those pre-existing failures are in test_web_server* / test_web_ui_build and look like in-process pollution that scripts/run_tests.sh isolates around; none are related to this change.

Documentation & Housekeeping

  • I've updated relevant documentation (docstrings on the new rule and helper) — the rule's rationale lives in its docstring alongside the other rules
  • N/A — no config keys added to cli-config.yaml.example (board_default_workdir is read from existing board metadata, not user config)
  • N/A — no architecture or workflow change
  • Cross-platform: pure stdlib string/dict work, no paths constructed or filesystem access in the rule
  • N/A — no tool descriptions/schemas changed

A `workspace_kind=worktree` task resolves against its own `workspace_path`
or the board's `default_workdir`. With neither, `_resolve_worktree_workspace`
raises rather than guess a repo. That is the right call, but it raises at
*dispatch*: the task burns its retries and the circuit breaker parks it as
`blocked` behind a message that reads like an infrastructure fault instead of
"this task was created with nowhere to run".

Multi-repo boards leave `default_workdir` unset on purpose, so a fallback
root can never check work out into the wrong repo. On those boards this is
the only way the mistake surfaces, and it surfaces after the retries are
gone. Add `worktree_without_checkout_root`, which reports the same condition
while the task is still queued: `error` when it is merely mis-created,
`critical` once it is already failing.

The rule mirrors the resolver rather than `project_id`. A project is what
usually supplies the path -- `create_task` turns a project link into
`<repo>/.worktrees/<id>` -- but it does so only at creation, and only when
the project has a primary repo; resolution never consults it again. Keying
on `project_id` would miss a task that has a link and no path.

It deliberately does not guess the repo. On a 24-task sample of exactly this
failure, inheriting the parent task's project would have misdirected 4 (17%),
every one of them from a direct parent, because cross-repo hand-offs are
normal rather than exceptional. A silent checkout into the wrong repo is
worse than a loud stall.

`board_default_workdir` is threaded through `config` so the dashboard judges
each task against its own board rather than whichever board is current; CLI
callers fall back to reading the current board.

Tests cover both severities, every silencing path (own path, board default,
other workspace kinds, terminal statuses), the project-linked-but-pathless
case, and an integration test pinning the rule to the resolver it predicts.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cron Cron scheduler and job management comp/plugins Plugin system and bundled plugins labels Sep 4, 2026
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

Summary

New kanban diagnostic rule worktree_without_checkout_root: worktree tasks with no workspace_path on a board with no default_workdir are reported while queued (error, escalating to critical once dispatch attempts burn), instead of failing opaquely at spawn. Threads the per-board default_workdir through CLI and dashboard paths so multi-board processes judge each task against its own board.

Findings

  • Non-blocking, correctness: hermes_cli/kanban_diagnostics.py:95-102 — fallback reads current-board metadata when the caller omits the key; the _BOARD_WORKDIR_UNSET sentinel distinguishes "unset" from "genuinely empty". Both callers now always supply it, so the fallback is defensive-only. Fine.
  • Non-blocking, note: rule correctly ignores done/archived (:91-93) and non-worktree kinds; project_id deliberately excluded from the firing condition with rationale documented (:66-72) and backed by the resolver-pinning test (test_worktree_without_checkout_root_matches_the_resolver). Thoughtful.
  • Dashboard threading (plugin_api.py:186,195,204,216) passes the requested board — fixes exactly the cross-board misjudgment the docstring warns about.

Verdict

Well-reasoned diagnostic with strong tests, including a resolver-parity pin. No concerns.

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 comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants