Skip to content

fix(kanban): decompose asks each child for its own repo, refuses unanchored worktree children - #103714

Open
lucascmg-vx wants to merge 1 commit into
NousResearch:mainfrom
lucascmg-vx:fix/decompose-child-repo-anchor
Open

lucascmg-vx wants to merge 1 commit into
NousResearch:mainfrom
lucascmg-vx:fix/decompose-child-repo-anchor

Conversation

@lucascmg-vx

Copy link
Copy Markdown

Problem

_insert_decomposed_child silently wrote workspace_kind=worktree children
with no workspace_path and no project_id whenever a decomposed task
named no repo and the board had no default_workdir configured. The child
could never be dispatched: it failed at spawn, burned its retries, and parked
as blocked behind a "missing workdir" message that reads like an
infrastructure fault rather than a decompose-time mistake.

project_id was also never written at all by _insert_decomposed_child,
independent of the anchoring bug above.

Why not just inherit the root's repo?

That was the first fix considered. Measured against every task that hit this
failure, the root's repo would have been the wrong checkout for 3 of 17
(18%) decomposed children with a known outcome — decomposition routinely fans
a single card out across repositories (e.g. a UI child and an API child
belonging to different checkouts). A silent checkout into the wrong repo is
worse than a loud stall at decompose time.

Fix

  • The decomposer's LLM contract now asks for a per-child "repo" (from the
    live registered-project list, injected into the prompt as {repos}).
  • _clean_children resolves a named repo into workspace_kind /
    workspace_path / project_id via a new _resolve_repo_choice. An unknown
    repo name is dropped with a log rather than fuzzy-matched (a fuzzy match on
    a name like "website" could silently resolve to the wrong of several
    similarly-named repos).
  • _insert_decomposed_child now writes project_id, and raises ValueError
    — inside the same transaction decompose_triage_task already uses — when a
    worktree child named no repo and the board has no default_workdir, instead
    of writing a half-anchored row that can never be dispatched.
  • Single-repo boards with default_workdir configured are unaffected: an
    unanchored child still resolves to workspace_path=None and dispatch
    materializes <repo>/.worktrees/<child-id> from the board anchor, exactly
    as before.
  • test_kanban_worktree_isolation.py's
    test_decompose_worktree_children_get_own_workspace now configures a board
    default_workdir, since it exercises that anchored path explicitly rather
    than relying on an unanchored worktree child being silently accepted.

Tests

New test_kanban_decompose_child_repo.py (14 cases): explicit per-child repo,
siblings on different repos, no inheritance from the root's repo, unknown repo
name dropped (not fuzzy-matched), loud atomic refusal of an unanchored
worktree child, and the board default_workdir anchor still working for a
child that names no repo.

tests/hermes_cli/test_kanban_decompose_child_repo.py .............. [100%]
tests/hermes_cli/test_kanban_decompose_db.py .......... [100%]
tests/hermes_cli/test_kanban_worktree_isolation.py ...... [100%]
22 passed

This is a companion to #103120 (worktree_without_checkout_root diagnostic):
that PR surfaces the mis-anchored task while it is still queued; this one
stops it from being created mis-anchored in the first place.

…chored worktree children

_insert_decomposed_child silently wrote worktree children with no
workspace_path and no project_id whenever a decomposed task named no
repo and the board had no default_workdir configured. The child could
never be dispatched: it failed at spawn, burned its retries, and
parked as blocked behind a message that reads like an infrastructure
fault rather than a decompose-time mistake.

Inheriting the root task's repo is not a safe fallback: measured
against every task that hit this failure, the root's repo was the
wrong checkout for 3 of 17 (18%) decomposed children with a known
outcome -- decomposition routinely fans a single card out across
repositories.

So the decomposer's LLM contract now asks for a per-child "repo"
(from the live registered-project list, injected into the prompt).
_clean_children resolves a named repo into workspace_kind /
workspace_path / project_id; an unknown name is dropped with a log
rather than fuzzy-matched. _insert_decomposed_child now writes
project_id (previously omitted unconditionally) and raises ValueError
-- inside the same transaction decompose_triage_task already uses --
when a worktree child named no repo and the board has no
default_workdir, instead of writing a half-anchored row.

Single-repo boards with default_workdir configured are unaffected: an
unanchored child still resolves to workspace_path=None and dispatch
materializes <repo>/.worktrees/<child-id> from the board anchor exactly
as before. test_kanban_worktree_isolation.py is updated to configure
one, since it now exercises that anchored path explicitly rather than
relying on an unanchored worktree child being silently accepted.

Adds test_kanban_decompose_child_repo.py (14 cases): explicit per-child
repo, siblings on different repos, no inheritance from the root's repo,
loud atomic refusal of an unanchored worktree child, and the board
default_workdir anchor still working.
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cron Cron scheduler and job management sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 5, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related / competing: #101629 fixes the same unanchored-decomposed-child failure by inheriting the root's repo anchor (the approach this PR argues against); #92519 rejects unanchored worktree tasks at create time and #103325 covers scratch children. Flagging the cluster so a maintainer can pick one mechanism.

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: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.

2 participants