Skip to content

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

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

chore(worktree): put worktrees under .claude and carry the overrides into them#145
anilcancakir merged 4 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.
@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

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

Looks correct - a two-file, config-only change whose three load-bearing claims I checked directly, and all three hold.

What I confirmed rather than assumed:

  • The ignore is as narrow as the comment says. git check-ignore -v .claude/worktrees/probe/x.dart.gitignore:49:.claude/worktrees/, and git ls-files .claude still lists all 10 .claude/rules/*.md. The rest of .claude/ stays tracked.
  • The .worktreeinclude entry actually has something to match: pubspec_overrides.yaml is gitignored at the bottom of .gitignore, so it passes the matches-AND-gitignored gate the file documents. The pattern is unanchored, so example/pubspec_overrides.yaml would be copied too if one ever exists - which is the behaviour you want here, not a problem.
  • Neither new file leaks into the published archive. dart pub publish --dry-run lists no root dotfiles at all (CHANGELOG.md, LICENSE, README.md, analysis_options.yaml, assets/, ...) - pub skips dot-prefixed paths, so .worktreeinclude is not shipped and .pubignore needs no new entry. 0 warnings and 1 hint, the hint being the pre-existing 1.0.0-alpha.13 version ordering, unrelated to this diff.

Putting worktrees under a dot-directory is also the safer of the two layouts for the same reason: dart pub publish, and the analyzer and formatter, all skip hidden directories, so a nested checkout cannot contaminate the golden-rule gates the way a sibling <repo>-<slug> inside the workspace could.

Minor

.worktreeinclude:12 — maintainability: the absolute-path requirement is the one thing that makes this work, and it is documented only inside .worktreeinclude, which nobody opens until it has already failed for them. CLAUDE.md is the contributor and agent entry point and mentions neither worktrees nor this constraint. Worth a line there, given the PR explains the machine-local file itself cannot carry the requirement. The failure mode is at least loud - a relative ../magic breaks version solving rather than silently resolving from pub.dev - so this harms nothing today.

Tests

Nothing covers this, and nothing can: the behaviour belongs to Claude Code's worktree creation, not to this package. The description's manual end-to-end verification is the appropriate substitute. No file under lib/ is touched, so the repo's post-change sync (CHANGELOG entry, doc/, skills/) correctly does not apply here - I checked that this diff claims no behaviour change it would need to document.

Checks I ran

  • mcp.read_pull_request_diff (manifest + both patches, scope: full) - 2 files, 26 additions, 0 deletions; no changed_without_diff, none dropped for size.
  • git check-ignore -v .claude/worktrees/probe/x.dart → matched .gitignore:49, exit 0.
  • git ls-files .claude → 10 tracked rule files, confirming the ignore does not swallow them.
  • dart pub publish --dry-run → resolved, archive 959 KB, 0 warnings and 1 hint; no dotfiles in the file listing.
  • Grep -i worktree across all tracked *.md / *.yaml / *.yml → no matches, so no existing doc describes the old <repo>-<slug> layout and none is left stale by this move.
  • Read .gitignore, .worktreeinclude, .pubignore, analysis_options.yaml, pubspec.yaml, CLAUDE.md, .claude/rules/ listing. No AGENTS.md or CONTRIBUTING.md exists in this repo.
  • Did not run flutter test or flutter analyze: no Dart source changed, so neither gate can observe this diff.

@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!

@anilcancakir

Copy link
Copy Markdown
Contributor Author

Fixed. You were right that .worktreeinclude is a file nobody opens until it has already failed for them, and CLAUDE.md mentioned neither worktrees nor the constraint.

It is now a row in Key Gotchas, which is where this repository already keeps "the thing that will bite you and the fix", rather than a new section. The failure mode you noted stays the reason it is only a Minor: a stale relative path breaks version solving loudly instead of resolving from pub.dev silently.

Also from another review in this sweep: git clean -xdf at the root now reaches a live worktree, since the directory is ignored rather than outside the repo. That is in the .gitignore comment.

@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 two new commits are documentation only - the CLAUDE.md gotcha resolves the Minor I raised, but the new git clean warning in .gitignore describes behaviour git does not have.

The absolute-path requirement is now in CLAUDE.md:94, which is exactly what I asked for; that finding is settled and I am not repeating it.

Minor

.gitignore:49 — correctness (docs): the warning is inverted. git clean -xdf does not wipe a live nested worktree - git's nested-repo guard recognises it and skips it; only -ff reaches it. A contributor reading this will either avoid a safe command or, worse, believe -ff is equally safe. Reproduced on git 2.43.0 with a worktree at .claude/worktrees/probe:

$ git clean -xdfn
Would skip repository .claude/worktrees/probe
$ git clean -xdf   # probe/f.txt, probe/.git, probe/.gitignore all still present
$ git clean -xdff
$ ls .claude/worktrees/probe
ls: cannot access '.claude/worktrees/probe': No such file or directory

The .git-is-a-file reasoning in the last sentence is the source of the error: git's guard tests for the presence of .git, not for it being a directory, so a worktree trips it the same way a nested clone does. git worktree prune after a -ff is still the right advice - just attach it to -ff.

.gitignore:48 — stray # at the end of repository.#, presumably a lost blank comment line when the paragraph was appended. Harmless (it is inside a comment, and git check-ignore -v .claude/worktrees/probe/x.dart still matches .gitignore:53), but it reads as a typo in a file whose comments are doing real explanatory work.

Tests

Unchanged from my last review: nothing covers this and nothing can, since the behaviour belongs to Claude Code's worktree creation. No file under lib/ is touched, so the post-change sync (CHANGELOG, doc/, skills/) still does not apply.

Checks I ran

  • read_pull_request_diff with since_sha=a830464scope: incremental, 2 files (.gitignore +5/-1, CLAUDE.md +1/-0), no changed_without_diff, none dropped.
  • Reproduced the git clean claim in a throwaway repo with a nested worktree at the same path: -xdfn printed Would skip repository, -xdf left the worktree intact, -xdff removed it. git version 2.43.0.
  • git check-ignore -v .claude/worktrees/probe/x.dart.gitignore:53, and git ls-files .claude → still 10 tracked rule files, so the stray # breaks nothing.
  • Read the full .worktreeinclude and the surrounding .gitignore block as they now stand.
  • Ran no analyze/format/test: no Dart, YAML or lib/ file changed in these commits, so no gate can observe them.

@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

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

Nothing new to raise - the only commit since my last review is the CLAUDE.md gotcha row I already read and accepted.

What changed since last time: 32d45e9 adds one row to the Key Gotchas table (CLAUDE.md:94) recording the absolute-path requirement. That is the fix for the Minor I raised first time round, it is in the right place, and the table renders correctly - 11 rows, consistent two-column shape. No other file moved.

The two .gitignore Minors from my previous review are untouched in this commit rather than answered, so they still stand as written and I am not restating them: the inverted git clean -xdf claim at .gitignore:49-52 (plain -xdf skips a live nested worktree; only -ff reaches it) and the stray # at the end of .gitignore:48. Both are comment-only and neither blocks anything.

Tests

Unchanged: nothing covers this and nothing can, since the behaviour belongs to Claude Code's worktree creation. No file under lib/ is touched, so the post-change sync (CHANGELOG, doc/, skills/) does not apply.

Checks I ran

  • read_pull_request_diff with since_sha=f3f07d6scope: incremental, 1 file (CLAUDE.md +1/-0), no changed_without_diff, none dropped.
  • git log --oneline → confirms 32d45e9 is the sole new commit on top of the reviewed f3f07d6.
  • Read the current .gitignore in full and the surrounding Key Gotchas table in CLAUDE.md.
  • Ran no analyze/format/test: this commit is one line of Markdown, so no gate can observe it.

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 of my outstanding Minors are fixed by the single new commit, and I reproduced every clause of the rewritten comment - nothing left to raise.

What changed since last review: 851e734 rewrites the git clean paragraph in .gitignore and drops the stray #. Comment-only, no other file touched.

I re-ran the same throwaway-repo probe against the new wording, and all four claims hold on git 2.43.0:

$ git clean -xdfn      → Would skip repository .claude/worktrees/probe
$ git clean -xdf       → probe/f.txt still present
$ git clean -xdff      → .claude/worktrees/ gone
$ ls .git/worktrees/   → probe
$ git worktree list    → .claude/worktrees/probe  [probe] prunable

So "resolves a gitfile too", "the contents survive", "-xdff … wipes a live worktree while .git/worktrees/<slug> survives", and the git worktree prune follow-up are each accurate as written - including the prunable registration the last sentence describes.

The ignore rule still bites after the comment reflow: git check-ignore -v .claude/worktrees/probe/x.dart.gitignore:55, and git ls-files .claude → 10 tracked rule files, so the rest of .claude/ remains tracked.

Tests

Unchanged: nothing covers this and nothing can, since the behaviour belongs to Claude Code's worktree creation. No file under lib/ is touched, so the post-change sync (CHANGELOG, doc/, skills/) does not apply.

Checks I ran

  • read_pull_request_diff with since_sha=32d45e9scope: incremental, 1 file (.gitignore +7/-5), no changed_without_diff, none dropped.
  • Reproduced the corrected git clean behaviour in /tmp with a worktree at .claude/worktrees/probe: dry run skipped, -xdf left contents intact, -xdff removed them, admin dir survived as prunable. git version 2.43.0.
  • git check-ignore -v .claude/worktrees/probe/x.dart.gitignore:55; git ls-files .claude → 10 files.
  • Ran no analyze/format/test: this commit is comment text in .gitignore, so no gate can observe it.

@anilcancakir
anilcancakir merged commit 78d540a into master Sep 3, 2026
6 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