Skip to content

v0.43.0

Choose a tag to compare

@yahor-punko yahor-punko released this 06 Aug 11:30
· 10 commits to main since this release

[0.43.0] — 2026-08-05

Added

  • --worktree-report and --prune-worktrees, two new operator flags for the harness-created .claude/worktrees/agent-* pile that accumulates without bound (T-559) — --worktree-report classifies every linked git worktree, read-only, into dirty / unintegrated / clean-and-integrated via patch-equivalence (git cherry semantics), not raw reachability: this project integrates by cherry-pick, so an integrated worktree's tip is unreachable from main by construction, and merge-base/branch --merged would have misclassified every one of them as still-open work. --prune-worktrees removes only the clean-and-integrated class, additionally gated on a 1-hour mtime safety window so a just-spawned agent's still-clean worktree is never swept mid-flight. State the full destructive scope plainly, since it is easy to underestimate from the name alone: a real prune does not just delete the worktree directory — it also force-deletes the branch ref via git branch -D. The -D (force) form is necessary, not careless: -d refuses every one of these branches outright, because integration here is by cherry-pick and no branch is ever "merged" by git's own reachability test — the same fact that forced patch-equivalence classification in the first place. Deletion only proceeds after a successful git worktree remove, wrapped in try/catch. --prune-worktrees defaults to dry-run (prints what would be removed, touches nothing) and requires an explicit --yes to act; run the real (--yes) prune only with zero live sub-agents, since every linked worktree of a repo shares one .git object/ref database and this is a repository-global exclusive-resource operation. Adopter-visible output change — not breaking: --close-session (both interactive and non-interactive modes) now also prints a one-line worktree-hygiene advisory from the same classifier whenever .claude/worktrees is non-empty — purely informational, it never prunes anything itself, and nothing that parses --close-session's existing output needs to change.
  • Bootstrap now seeds/merges VS Code exclusion settings for .claude/worktrees (T-577) — a fresh install and every --update additively and idempotently merge three keys into the target project's .vscode/settings.json: files.exclude, search.exclude, and files.watcherExclude, each carrying a **/.claude/worktrees glob set to true (the watcher key uses the deeper **/.claude/worktrees/** glob, matching VS Code's own shipped convention for that surface). The merge follows the same contract as the existing managed-hooks merge: only the single managed glob sub-entry is ever added under each key, every other key and every other sub-entry already present survives byte-identical, and a pre-existing conflicting value for the managed sub-entry (anything other than true) is left untouched with a printed notice rather than a silent overwrite — a second run makes no further change. Read the residual before relying on this for the problem that motivated it. The field report behind this task was VS Code's Git extension surfacing the accumulated worktree checkouts as phantom repository state (down to an operator asking why ~10K lines of changes were queued). These three keys are expected to also suppress that SCM-panel symptom, not just the file-explorer/search/watcher noise — the reasoning is that VS Code's git extension is understood to reuse the same files.exclude-driven skip logic for its own nested-repository discovery scan — but this is NOT verified against a live VS Code session: driving a GUI is outside what an agent can execute, so the SCM-discovery half of this fix is documented-but-unconfirmed. If it doesn't hold in practice, the documented fallback lever is git.autoRepositoryDetection: "openEditors" (scope nested-repo detection to files that are actually open), added manually to your own .vscode/settings.json — see docs/core/BOOTSTRAP_GUIDE.md, "VS Code worktree exclusion". This release deliberately does not seed git.openRepositoryInParentFolders: that setting is repo-global (affects every parent-folder repo the workspace might sit inside) with legitimate-use collateral, and independently of that it is the wrong lever for this specific problem — it governs discovery of repos in PARENT folders, not the SUBFOLDER nested-repo case .claude/worktrees/agent-* actually is.

Fixed

  • The worktree-hygiene regression fixture (test-worktree-hygiene.js, T-559) hard-depended on the host machine's git init default branch, which broke CI on ubuntu runners (T-632) — both git init call sites in the fixture now explicitly pin -b main (the same pin already used by check-assembled-suite.js), so classifyWorktrees(mainDir, { mainRef: 'main' }) resolves correctly regardless of the host's init.defaultBranch setting. Upstream git still defaults git init to master, so on any host without a main-default override, the fixture's repo never had a main ref, every worktree's patch-equivalence check fell through to 'unknown', and Test 1 (and, less visibly, Tests 2/3/5) asserted 'unintegrated' where 'clean-and-integrated' was expected. This stayed invisible in local development because Apple Git 2.50.1 patches git init to default to main even with global/system config suppressed — the CI runners run upstream git, where the fixture's dependency on the host default was exposed.
  • classifyWorktrees() conflated an unresolvable mainRef with genuine per-worktree unintegrated work, so an operator on any repo where the default mainRef ('main') doesn't exist got a confidently wrong report instead of an error (T-633) — the escaped defect T-632 fixed at the fixture layer left the classifier itself unguarded: on a real master-default repo, --worktree-report's default invocation reported a genuinely cherry-pick-integrated worktree as unintegrated and exited 0, and --prune-worktrees reported "no prunable worktrees" and also exited 0 — no error either direction, and the exact inverse of reality. classifyWorktrees() now verifies once, up front, that mainRef resolves to a commit, and throws a typed UnresolvableMainRefError before doing any per-worktree work when it doesn't; the pre-existing conservative fallback for an individually broken worktree (missing object, getPatchEquivalenceStatus()'unknown' → classified 'unintegrated') is unchanged and now has its own dedicated test. --worktree-report and --prune-worktrees catch the typed error and exit non-zero, naming the unresolved ref and pointing at --main-ref, before printing any per-worktree classification; --close-session's shared advisory degrades to a single line naming the unresolved ref instead of silently going quiet — close-session itself still always completes.

Docs

  • Harness worktree placement is now documented as untrusted, with a mandatory Base floor: preflight (T-621) — the developer role spec gained a start-of-run check, keyed on a brief-supplied Base floor: <hash>, that runs git log --oneline HEAD..<floor> before any edit and requires the exact output quoted in the report: empty means the worktree's base is at or after the floor (proceed), non-empty means the worktree is missing required history (stop — stale base), and fatal: Invalid revision range means the worktree isn't even in the target repository (stop — wrong repo). docs/core/ORCHESTRATION_RULES.md's GAP C is corrected from a mechanism claim to an observation — harness-created worktree bases have been seen arbitrarily stale, sticky across separate runs, and not an ancestor of any live branch, and neither of the two candidate causes is established, so neither is written into a brief or doc as "the" mechanism. A new GAP E documents the companion fact that isolation itself is scoped to the spawning session's own repo, so a cross-repo task's worktree can land under an unrelated repo's tree with zero isolation for the actual target — this is stated as a documented scoping fact, not a defect being fixed. Both CLAUDE.md and docs/AGENT_SPEC.md gained the optional Base floor: brief field.
  • docs/AGENT_SPEC.md's copy of the sub-agent brief template had silently drifted from CLAUDE.md's — missing five fields and degraded completion-token wording — and is now both fixed and mechanically guarded against drifting again (T-623) — AGENT_SPEC.md's template gained Adjacent docs read:, Read current main:, Model:, Effort:, and Turn budget:, plus a "Before exiting" line that now references the completion-token marker the way CLAUDE.md's already did. scripts/test-agent-spec-sync.js gained a parity check that parses both documents' brief-template fences, compares their field-name sets, and asserts both Before exiting: lines carry the completion-token marker — wording differences between the two copies are expected and not a failure; only the field-name set and the marker's presence are checked. A field added to one template and not the other will now fail the shipped test suite instead of silently drifting a second time.