Skip to content

fix: claim parser misses paren form, so conflict guard never fires #209

Description

@Jammy2211

Overview

worktree_check_conflict is the start_dev step-6 guard that decides whether a new task registers in active.md (can start) or planned.md (blocked). It exited 0 for roughly half of all real claims, so genuinely-colliding tasks were free to proceed in parallel.

worktree_list_claimed (bin/worktree.sh) split each - <repo> bullet on ": " — the schema the start_library/start_workspace references document. Writers also emit - <repo> (<branch>), which the split swallowed whole: repo became autolens_workspace (feature/foo), never equalled the requested repo name, and the guard reported no conflict. Measured across active.md history the two forms run 162 colon-form to 139 paren-form — this was not an edge case.

Blast radius is wider than routing alone. skills/repo_cleanup/reference.md defines the CLAIMED protection set as the (repo, branch) pairs from this function, so malformed repos and empty branches left in-flight branches without that guard. IN_WORKTREE and OPEN_PR still covered them, so nothing was lost.

Plan

  • Pin the failure first: drive the real bash functions against fixture ledgers and confirm the paren-form case fails before touching the parser.
  • Accept both claim forms so the bare repo name — the only field the guard compares — survives either shape.
  • Buffer claim rows per task and flush at the entry boundary, so worktree: is captured whichever side of the repos: block it sits on.
  • Validate against the real ledger rather than fixtures alone: every historical claim line, cross-checked against repos.yaml.
  • Keep both forms accepted instead of migrating the ledger, and say so in the two references that document the schema.
Detailed implementation plan

Affected Repositories

  • PyAutoBrain (primary, and only)

Branch Survey

Repository Current Branch Dirty?
./PyAutoBrain claude/automind-task-planning-ef00l2 clean

Branch: claude/automind-task-planning-ef00l2

Implementation Steps

  1. tests/test_worktree_conflict_guard.py — new, on the test_worktree_claim_guard.py idiom (real bash against a temp PYAUTO_MAIN fixture). Cases: paren-form conflict; colon-form conflict; bare repo with no branch; trailing notes on both forms; task never conflicts with itself; unclaimed repo exits 0; conflict names every claiming task; worktree: captured regardless of field order and never leaking between tasks; missing active.md yields no claims.
  2. bin/worktree.shworktree_list_claimed — rewrite the awk block:
    • match ": " first, else a (-delimited branch, else treat the whole line as a bare repo name; strip trailing whitespace from repo.
    • accumulate repos[]/branches[] per task, emit via a flush() called on /^## / and at END, replacing the emit-on-sight behaviour that made wt order-dependent.
  3. skills/start_library/start_library.md, skills/start_workspace/reference.md — state that both forms parse and that the branch is optional. Corrects a factual error in the former, which claimed worktree: is what the guard reads; the - <repo> bullets are the claim.

Validation

  • tests/test_worktree_conflict_guard.py — confirmed failing on all four defects against the unfixed parser, then green.
  • Full suite: 250 passed.
  • Corpus check: all 258 claim lines from active.md history parse to a bare repo name, 0 malformed, and all 32 distinct names resolve against PyAutoMind/repos.yaml.

Key Files

  • bin/worktree.shworktree_list_claimed (the parser) and worktree_check_conflict (unchanged; it was reading bad input).
  • tests/test_worktree_conflict_guard.py — the new guard tests.
  • skills/repo_cleanup/reference.md — documents the CLAIMED protection set that also consumed the malformed output.

Notes on the prompt's premise

The prompt states the paren form is "what every skill and every existing entry writes." Measured, it is ~53/47 colon-to-paren, and both remain in active use — which is why dual-form tolerance is the fix and re-aligning the docs on one form is not. The prompt's Difficulty: small holds; the Bug Agent scored it large (8), prose-driven, on the mge-sigma-min-workspace-sweep sizing-note precedent.

Original Prompt

Click to expand starting prompt

worktree_check_conflict has never detected a conflict

Type: bug
Target: pyautobrain
Repos:

  • PyAutoBrain
    Difficulty: small
    Autonomy: safe
    Priority: high

PyAutoBrain/bin/worktree.shworktree_check_conflict always exits 0. The
start_dev step-6 conflict guard, which decides whether a new task registers in
active.md (can start) or planned.md (blocked), has therefore never fired for
any task.

Root cause

worktree_list_claimed (worktree.sh:309-335) parses PyAutoMind/active.md with:

/^  - [A-Za-z]/ {
  gsub(/^  - /, "")
  split($0, parts, ": ")
  repo   = parts[1]
  branch = parts[2]
  printf "%s\t%s\t%s\t%s\n", task, repo, branch, wt
}

It expects - PyAutoFit: feature/foo. But active.md is written today as:

- repos:
  - autolens_workspace (feature/multistart-prodigy-start-here)

No ": " separator. So parts[1] swallows the whole line and repo becomes
autolens_workspace (feature/multistart-prodigy-start-here), with branch
empty. Confirmed live:

$ source PyAutoBrain/bin/worktree.sh && worktree_list_claimed
multistart-prodigy-start-here	autolens_workspace (feature/multistart-prodigy-start-here)		~/Code/PyAutoLabs-wt/multistart-prodigy-start-here

worktree_check_conflict (worktree.sh:340-355) then compares
"$existing_repo" == "$want", i.e. "autolens_workspace (feature/...)" against
"autolens_workspace". Never equal → rc stays 0 → "no conflict", always.

Reproducer (autolens_workspace is claimed by two active tasks as of
2026-07-29):

source PyAutoBrain/bin/worktree.sh
worktree_check_conflict some-new-task autolens_workspace; echo "exit=$?"
# exit=0   <-- should be 1

Fix

Parse both shapes in the worktree_list_claimed awk block — the current
- <repo> (<branch>) form and the legacy - <repo>: <branch> form the
awk was written for — so repo is always the bare repo name. The branch is
informational (used only in the conflict message), so tolerate it being absent.

Do not "fix" this by rewriting active.md into the colon form: the
paren form is what every skill and every existing entry writes, and
[[feedback_active_md_dash_repos]] records that these - Repo lines are the
claims. The parser is what is wrong.

Note the schema drift runs both ways —
PyAutoBrain/skills/start_workspace/reference.md ("active.md registration")
still documents the colon form the awk expects:

- repos:
  - PyAutoFit: feature/<task-name>

So the parser matches the documented schema and the writers drifted away from
it. Accepting both forms fixes the guard without a migration; whether to also
re-align the docs on one form is a separate call.

Validation

  • worktree_check_conflict <new-task> autolens_workspace exits 1 and names both
    multistart-prodigy-start-here and assistant-start-here-scripts.
  • worktree_check_conflict multistart-prodigy-start-here autolens_workspace
    still exits 0 (a task never conflicts with itself).
  • worktree_list_claimed emits the branch in its own column again.
  • Repos claimed by no task still exit 0.

Notes

  • Found 2026-07-29 while running start_dev for
    draft/docs/workspaces/likelihood_function_jax_section_to_pointer.md; the
    guard reported no conflict on two repos that two active tasks both claim.
  • Blast radius is the routing decision only — it makes start_dev register
    every task as startable, so genuinely-colliding tasks silently proceed in
    parallel instead of queueing in planned.md. Every parallel-claim decision
    made to date was made by a human reading worktree_list_claimed by eye, not
    by the guard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions