Skip to content

chore(worktree): put worktrees under .claude and carry the overrides into them - #18

Merged
anilcancakir merged 3 commits into
masterfrom
chore/worktree-layout
Sep 3, 2026
Merged

chore(worktree): put worktrees under .claude and carry the overrides into them#18
anilcancakir merged 3 commits into
masterfrom
chore/worktree-layout

Conversation

@anilcancakir

Copy link
Copy Markdown
Contributor

What

  • .gitignore: ignore .claude/worktrees/ (only that subdirectory; the rules under .claude/ stay tracked)
  • .worktreeinclude: new, copies pubspec_overrides.yaml into every worktree Claude Code creates

Why

Worktrees were opened beside the checkout as <repo>-<slug>, so they landed in the workspace directory next to the real repositories, and Claude Code's EnterWorktree could not be used at all: it writes to a fixed .claude/worktrees/<slug>.

Moving them inside exposes a problem the old layout hid. pubspec_overrides.yaml is gitignored, so a worktree never receives it, and the absence is silent rather than loud: the siblings resolve from pub.dev, flutter pub get succeeds, and the suite passes against the PUBLISHED packages while the diff under review is of the local ones. An unreleased sibling API is where that bites.

For the copy to be correct the local file needs ABSOLUTE paths, since a relative ../magic resolves to .claude/worktrees/magic from inside a worktree. That file is machine-local and not committed, so this PR cannot carry it; the requirement is documented in .worktreeinclude.

Testing

The pattern was proven end to end on fluttersdk/magic_starter#124 first: probe worktree opened under the new path, pubspec_overrides.yaml confirmed absent, copied in, flutter pub get resolved and package_config.json pointed at the local checkouts. Here I verified that every rewritten absolute path resolves to a real package directory and that flutter pub get still succeeds.

No Dart, YAML or lib/ file is touched, so analyze, format and test have nothing to say about this diff.

…into them

Worktrees used to sit beside the checkout as <repo>-<slug>, which put them in the workspace directory next to the real repositories and left EnterWorktree unusable, since that tool writes to a fixed .claude/worktrees/<slug>. Pattern proven on fluttersdk/magic_starter#124.
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

Two tooling-config files, no Dart touched; both ignore rules behave as described and I found nothing that breaks, only two documentation gaps.

Minor

.gitignore:48 — the comment says "the rest of .claude/ is tracked, because the rules under it are part of the repository", but nothing under .claude/ is tracked in this repo, and CLAUDE.md says so itself ("the package is small enough that it has no path-scoped rules under .claude/rules/ yet"). git ls-files '.claude*' returns nothing. The narrow pattern is still the right call, but a reader following the comment will go looking for tracked rules that do not exist. (maintainability)

CLAUDE.md — the absolute-path requirement for pubspec_overrides.yaml is documented only inside .worktreeinclude, which is a file a developer wiring up local siblings has no reason to open. CLAUDE.md's own "Local sibling development goes through the gitignored pubspec_overrides.yaml" line is where that constraint belongs, and it is the root spec by its own first paragraph. As it stands, anyone whose existing local override uses the natural relative ../magic form will now get flutter pub get failing in every worktree with no pointer to why. That failure is louder than the silent pub.dev resolve it replaces, which is the point of the change, so this is about discoverability rather than correctness. (maintainability)

Tests

Nothing to test: no Dart, YAML or lib/ file changed. No CHANGELOG.md entry, correctly, since golden rule 3 scopes that to behavioural or interface changes and this is neither.

Checks I ran

  • git check-ignore -v pubspec_overrides.yaml .claude/worktrees/x — both match, on .gitignore:30 and .gitignore:50 respectively, so the new pattern ignores the worktree subdirectory and only that, and the file .worktreeinclude names is genuinely gitignored (a copy candidate must be both matched and ignored).
  • git status --porcelain — clean, so a worktree placed inside the checkout does not dirty the parent tree.
  • git ls-files '.claude*' — empty; this is the evidence for the first finding.
  • dart format --set-exit-if-changed --output=none . — "Formatted 16 files (0 changed)", exit 0.
  • grep -P '[\x{2013}\x{2014}]' over both files — no en or em dashes, per the style rule.
  • flutter analyze and flutter test not run: dependencies are unresolved in this checkout (dart format warned it could not resolve package:flutter_lints/flutter.yaml) and the diff contains no analyzable code. I also could not empirically confirm whether a root flutter analyze descends into .claude/worktrees/, since creating a probe file there was not permitted; analysis_options.yaml does not exclude the path, though the analyzer's usual handling of dot-prefixed directories should cover it.

@anilcancakir

Copy link
Copy Markdown
Contributor Author

Both fixed.

The .gitignore comment was wrong here specifically: git ls-files .claude returns nothing in this repository, so the sentence about tracked rules described a sibling repo. The comment now says only this subdirectory is ignored so anything else landing under .claude/ stays visible, and that nothing is tracked there today. Applied the same check across the sweep; wind_diagnostics_contracts was the other one and got the same wording.

The absolute-path requirement is now in CLAUDE.md:44, right after the line about local sibling development that you pointed at. That was the right place: it is where someone wiring up overrides is already reading.

@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

Both earlier findings are settled: the .claude claim is now correct and the absolute-path requirement landed in CLAUDE.md:47. The new commit adds a git clean paragraph that I could not reproduce, plus a stray character.

Minor

.gitignore:50 — the new paragraph says git clean -xdf "wipes a live worktree's contents" and that "a worktree's .git is a file rather than a directory, so git's nested-repo guard does not stop it". Both halves are the opposite of what git does: the guard reads the gitfile, recognises the directory as a repository, and skips it. Only the double-force -xdff removes it, and it takes all of .claude/ with it rather than leaving a dangling registration. A reader who trusts this will run git worktree prune looking for a stale entry that is not there. (maintainability)

Reproduced in a throwaway repo with a worktree at .claude/worktrees/slug, that path gitignored:

$ git clean -xdn
Would skip repository .claude/worktrees/slug
$ git clean -xdf          # worktree intact afterwards
Skipping repository .claude/worktrees/slug
$ git worktree list
/tmp/ctest/main                         a6da65d [master]
/tmp/ctest/main/.claude/worktrees/slug  a6da65d [wt]
$ git clean -xdff
Removing .claude/

The warning is still worth keeping if it is rewritten around -xdff, since that is a real foot-gun for a worktree inside the tree.

.gitignore:49Nothing is tracked there today.# carries a stray trailing #. Harmless, since the line is a comment either way, but it reads as a typo. (maintainability)

Tests

Nothing to test: the two changed files are a comment block and prose. No CHANGELOG.md entry, correctly, since golden rule 3 scopes that to behavioural or interface changes.

Checks I ran

  • git check-ignore -v .claude/worktrees/x .claude/rules/foo.md pubspec_overrides.yaml — the first and third match (.gitignore:54 and :30), .claude/rules/foo.md does not. That is the evidence that the corrected comment on line 48 is now accurate: only the worktrees subdirectory is ignored.
  • git ls-files '.claude*' — still empty, matching the new "nothing is tracked there today".
  • Throwaway repo with a nested worktree, git clean -xdn / -xdf / -xdff — output quoted above; this is the basis for the first finding.
  • git status --porcelain — clean.
  • grep -P '[\x{2013}\x{2014}]' over .gitignore, CLAUDE.md and .worktreeinclude — no en or em dashes, per the style rule.
  • flutter analyze and flutter test not run: dependencies are unresolved in this checkout and this increment contains no analyzable code.

The comment claimed plain -xdf wipes a live worktree because its .git is a file rather than a directory. Measured on a scratch repo: the dry run prints "Skipping repository .claude/worktrees/slug", the worktree survives -xdf with its untracked files intact, and only -xdff removes it and leaves the registration prunable. The guard keys on the gitlink, not on the form of .git. Also splits the stray # that joined the two comment blocks.
@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

Both earlier findings are settled and I have no new ones: the git clean paragraph now matches what git actually does, verified line by line against a fresh repro, and the stray # is gone.

Since the last review: one commit, touching .gitignore only. It rewrites the git clean paragraph so -xdf is described as skipping the worktree and -xdff as the destructive case, and drops the trailing # from the "Nothing is tracked there today." line.

Every claim in the new paragraph checks out in a throwaway repo with a worktree at .claude/worktrees/slug, that path gitignored:

$ git clean -xdn
Would skip repository .claude/worktrees/slug
$ git clean -xdf
Skipping repository .claude/worktrees/slug     # contents survive
$ git clean -xdff
Removing .claude/
$ ls -a .git/worktrees
.  ..  slug                                    # registration survives
$ git worktree list
/tmp/ctest2/main/.claude/worktrees/slug  ec53160 [wt] prunable
$ git worktree prune -v -n
Removing worktrees/slug: gitdir file points to non-existent location

So "-xdf leaves them alone", the gitfile-resolving guard, "-xdff does not skip them", the surviving .git/worktrees/<slug> and the git worktree prune follow-up are all correct as written.

Tests

Nothing to test: the sole changed file is a comment block in .gitignore. No CHANGELOG.md entry, correctly, since golden rule 3 scopes that to behavioural or interface changes.

Checks I ran

  • Throwaway repo with a nested worktree, git clean -xdn / -xdf / -xdff, then git worktree list and git worktree prune -n - output quoted above; this is the evidence the paragraph is now accurate.
  • git check-ignore -v .claude/worktrees/x .claude/rules/foo.md pubspec_overrides.yaml - the first and third match (.gitignore:56 and :30), .claude/rules/foo.md does not, so the narrow pattern still ignores only the worktrees subdirectory.
  • git ls-files '.claude*' - empty, matching "Nothing is tracked there today".
  • sed -n '40,50p' CLAUDE.md - the absolute-path requirement is present at CLAUDE.md:47, immediately after the local-sibling line, unchanged by this commit.
  • grep -P '[\x{2013}\x{2014}]' over .gitignore, CLAUDE.md and .worktreeinclude - no en or em dashes, per the style rule.
  • git status --porcelain - clean.
  • flutter analyze, dart format and flutter test not run for this increment: the only changed file is a .gitignore comment, and dependencies are unresolved in this checkout.

@anilcancakir
anilcancakir merged commit 3b98181 into master Sep 3, 2026
4 checks passed
@anilcancakir
anilcancakir deleted the chore/worktree-layout branch September 3, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant