Skip to content

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

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#24
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.

Looks correct - two config files, no code paths touched, and the two ways this layout could leak into the package or the toolchain both check out clean.

What I checked rather than assumed:

  • .claude/worktrees/ is a nested path inside the working tree, so the risks are (a) shipping it to pub.dev and (b) the analyzer walking into it. Neither applies: dart pub publish --dry-run lists no dotfiles at all (.worktreeinclude is absent from the archive, as is .claude/, independent of .pubignore), and the analyzer skips directories whose name starts with ., so a worktree's lib/ can never be analysed twice from the parent checkout.
  • The .gitignore entry is scoped to the subdirectory, so .claude/rules/, .claude/commands/ and settings.json stay tracked - confirmed those are still present and tracked in this checkout.
  • .worktreeinclude's premise holds: pubspec_overrides.yaml is gitignored at .gitignore:69, so it both matches the include list and qualifies for copying, and no tracked file can be duplicated by the single entry.
  • .claude/settings.json registers only PostToolUse hooks, no WorktreeCreate, so the default copy logic the file relies on is in fact the one that runs here. The caveat in the last comment block is the right thing to have written down.

The absolute-path requirement for pubspec_overrides.yaml is real and cannot be carried by a commit, and documenting it in the file that depends on it is the right place for it.

Tests

Nothing covers this and nothing should - the behaviour lives in Claude Code's worktree creation, not in this package.

Checks I ran

  • flutter pub publish --dry-run - 0 warnings; .worktreeinclude not in the 255 KB archive.
  • flutter pub get (via the dry-run resolve) - resolved successfully.
  • Read both changed files in full; skipped analyze/format/test, since no Dart or YAML file is touched.

@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

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

Since my last review the only new commit is 393c8b1, which adds a comment block to .gitignore and nothing else - but the mechanism it documents does not reproduce: git's nested-repo guard does stop git clean -xdf.

Minor

.gitignore:52 — The note says git clean -xdf wipes a live worktree's contents and leaves a stale registration, because a worktree's .git is a file rather than a directory so the nested-repo guard does not apply. Probed it on a scratch repo with a worktree at .claude/worktrees/slug: the dry run printed Would skip repository .claude/worktrees/slug, and after git clean -xdf the worktree was fully intact, untracked scratch file included. The guard keys on the gitlink, not on .git being a directory, so a file-form .git is skipped just the same. Only the double-force git clean -xdff produces the state described - contents gone, git worktree list reporting the slug as prunable. The hazard is real, the trigger is one flag narrower than written; -xdff is the accurate wording. (maintainability)

.gitignore:51 — Stray # at the end of # ... part of the repository.#, left over from joining the two comment blocks. Harmless to git, since the whole line is a comment.

Tests

Nothing covers this and nothing should - it is a comment in an ignore file.

Checks I ran

  • git worktree add .claude/worktrees/slug + git clean -xdn in a scratch repo (git 2.43.0) - Would skip repository .claude/worktrees/slug.
  • git clean -xdf in that repo - worktree contents survived intact; git clean -xdff removed them and left the registration prunable.
  • git check-ignore -v .claude/worktrees/foo/lib/x.dart - matches .gitignore:56, so the ignore rule still resolves after the comment edit.
  • git ls-files .claude - the four rule/command files and settings.json are still tracked.
  • Skipped analyze/format/test: this commit touches only .gitignore.

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.
@anilcancakir

Copy link
Copy Markdown
Contributor Author

You are right and I had it backwards. Reproduced your probe independently before changing anything:

$ git clean -xdf --dry-run
Skipping repository .claude/worktrees/slug

$ git clean -xdf        # worktree intact, its untracked scratch file too
$ ls .claude/worktrees/slug/
untracked.txt

$ git clean -xdff       # gone
$ git worktree list --porcelain | grep -c prunable
1

So the guard keys on the gitlink, exactly as you said, and my stated reason (that a file-form .git slips past it) was wrong as well as the command. The comment now says -xdf leaves them alone and names -xdff as the one that does not, with the "Skipping repository" line quoted so the next reader can recognise it.

The stray # is fixed in the same commit; it came from joining two comment blocks without the separating newline, and it went out to every repository in this sweep, so the fix did too.

@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

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

One new commit since my last review, 59fbf2f, which rewrites the .gitignore comment block - it now matches what git actually does, and both of my earlier findings are closed.

Re-probed rather than taking the corrected wording on trust (git 2.43.0, scratch repo, worktree at .claude/worktrees/slug with an untracked scratch.txt):

  • git clean -xdfSkipping repository .claude/worktrees/slug, contents intact including the scratch file. This is the exact string the comment quotes, and it is the non-dry-run form, so a reader who runs -xdf will see it verbatim; the dry run prints Would skip repository ... instead, which is the only wording difference and not worth spelling out.
  • git clean -xdffRemoving .claude/, worktree gone, git worktree list --porcelain reports one prunable entry. So -xdff is the flag that produces the state described, exactly as the comment now says.
  • The stated reason is right too: the guard resolves the gitfile, it is not defeated by .git being a file.

The stray trailing # is gone, and the block reads as two paragraphs separated by a bare # line.

Ignore behaviour is unchanged by the edit: git check-ignore -v .claude/worktrees/foo/lib/x.dart still matches .gitignore:58, and git ls-files .claude still lists the three rule files, commands/release.md and settings.json.

Tests

Nothing covers this and nothing should - it is a comment in an ignore file.

Checks I ran

  • git worktree add .claude/worktrees/slug then git clean -xdn / -xdf / -xdff on a scratch repo - output as quoted above; the comment's claims reproduce.
  • git check-ignore -v .claude/worktrees/foo/lib/x.dart - matches .gitignore:58.
  • git ls-files .claude - five files still tracked.
  • Skipped analyze/format/test: this commit touches only .gitignore.

@anilcancakir
anilcancakir merged commit d23f1f3 into master Sep 3, 2026
4 checks passed
@anilcancakir
anilcancakir deleted the chore/worktree-layout branch September 3, 2026 18:04
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