Skip to content

fix(kanban): auto-promote scratch workspace to worktree for code-fix tasks - #85087

Open
matsromblad wants to merge 1 commit into
NousResearch:mainfrom
matsromblad:fix/scratch-workspace-auto-promote
Open

matsromblad wants to merge 1 commit into
NousResearch:mainfrom
matsromblad:fix/scratch-workspace-auto-promote

Conversation

@matsromblad

Copy link
Copy Markdown

Problem

When a Kanban task is created without --project (legacy imports, manual dashboard creates, older code paths), resolve_workspace in hermes_cli/kanban_db.py lands it under <board-root>/workspaces/<id>/ — an empty tmp dir. Workers running such tasks must cd into the source tree manually on every turn, and any tool that resolves paths against TERMINAL_CWD lands in the empty dir instead of the project repo.

This has been silently affecting auto-improve-miner-dispatched tasks since early August 2026 (the miner imports Tier-1/Tier-2 audit findings via tools/kanban_github_sync/import_github_issues.py, which didn't bind --project until a parallel fix was added).

Fix

Add a body-driven auto-promotion helper in hermes_cli/kanban_db.py:

  • _CODE_TASK_MARKERS — narrow tuple of source-tree tokens (src/agents/, tests/test_, docs/audits/, pytest, ruff, git worktree). Kept narrow on purpose: false positives waste a worktree under a real registered repo; false negatives are recoverable (the worker can manually cd).
  • _maybe_promote_scratch_to_worktree(task) — returns the matching project's primary_path if both conditions hold, else None. Substring match on the project's slug / name / repo basename — loose-by-design but safe (only matches project names from the user's own projects.db).
  • resolve_workspace calls the helper at the top of the scratch branch; on match it mutates the task in-memory to workspace_kind='worktree' and delegates to the existing _resolve_worktree_workspace path.

The helper fail-closes on projects_db outage: any exception → None. Tested explicitly in test_no_promotion_when_projects_db_lookup_fails.

Tests

tests/hermes_cli/test_scratch_to_worktree_promotion.py (6 cases, 6/6 green):

  1. Promote on src/agents/ + project basename in body
  2. Promote on tests/test_ + project basename
  3. Promote on pytest + project basename
  4. No promote for non-code task body
  5. No promote when code markers present but no registered project matches
  6. Fail-closed: helper returns None if projects_db.connect_closing raises

Existing test suite verified — 52 pass, 4 fail (all pre-existing on main at HEAD~1: test_protocol_violation_budget_not_consumed_by_other_failures, test_rate_limit_exit_requeues_without_counting_failure, test_worktree_workspace_explicit_target_materializes_linked_worktree, test_resolve_hermes_argv_falls_back_to_module_form_when_no_path_shim). No regressions introduced.

ruff check clean on both files.

Companion fix

tools/kanban_github_sync/import_github_issues.py (Chiron-side, separate repo) sends --project <slug> on the importer side as of today. This Hermes-side patch is the dispatcher-side fallback for tasks created without that binding (legacy imports, manual creates, future code paths). They are independent: each one closes the loop on its own failure mode, and together they leave no path where a code-fix task lands in an empty scratch workspace.

…tasks

When a task is created without `--project` (legacy imports, manual
dashboard creates, or older code paths), `resolve_workspace` lands it
under `<board-root>/workspaces/<id>/` — an empty tmp dir. Workers
running such tasks must `cd` into the source tree manually on every
turn, and any tool that resolves paths against TERMINAL_CWD lands
in the empty dir instead of the project repo.

Auto-promote at resolve time: if the task body contains code-fix
markers (`src/agents/`, `tests/test_`, `docs/audits/`, `pytest`,
`ruff`, `git worktree`) AND a registered project shares a basename
substring with the body, promote the workspace to a linked worktree
on that project's primary_path. The body never names an absolute path
we would trust — only project basenames from the user's own projects.db
— so this is loose-by-design but safe: a false match wastes a worktree
under a real registered repo, not under an attacker-chosen path.

This is the dispatcher-side complement to the project-binding fix in
`tools/kanban_github_sync/import_github_issues.py` (which sends
`--project <slug>` on the importer side). Together they close the
loop on a class of Kanban tasks that have been silently landing in
empty workspaces since the auto-improve miner started dispatching
Tier-1/Tier-2 audit findings in early August.

Tests in `tests/hermes_cli/test_scratch_to_worktree_promotion.py`:
- 3× positive (src/agents/, tests/, pytest + project basename in body)
- 1× negative (non-code task body)
- 1× negative (code markers but no registered project matches)
- 1× fail-closed (projects_db unreachable → None)
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference, author can ignore or act on any point.

fix(kanban): auto-promote scratch workspace to worktree for code-fix tasks — dispatcher-side promotion from scratch to a linked worktree when the body signals a code-fix task and a registered project matches. Reasonable heuristic with good fail-closed tests.

  1. Persistence question: resolve_workspace mutates the in-memory task (workspace_kind='worktree', workspace_path=...) but the diff does not show the promotion being persisted to the board DB. If it is not persisted, every subsequent dispatch pass re-enters the scratch branch, re-runs promotion, and calls _resolve_worktree_workspace again — which likely creates/reuses a worktree. Please confirm that path is idempotent (reuses an existing worktree for the same repo/task) or persist the promoted kind/path, otherwise repeated dispatches accumulate worktrees.
  2. _maybe_promote_scratch_to_worktree scoring has no minimum-length guard: score += len(needle) means a project whose name/slug is short and generic (e.g. "qa", "src", "test") and appears inside a marker substring in the body (e.g. src/qa/) can out-score the intended project when several projects exist. The docstring acknowledges looseness, but weighting slug/name over basename or requiring a minimum needle length would cut mis-promotions.
  3. Matching is case-sensitive on both markers and needles — PYTEST/Src/ in a body would not promote. Minor.
  4. test_no_promotion_when_projects_db_lookup_fails correctly pins the fail-closed behavior; the broad except Exception: return None also swallows programming errors, which is acceptable for a best-effort promotion but worth a log line for diagnosability.

@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 Aug 16, 2026

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 type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants