Skip to content

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

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#16
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.
@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.

The change looks correct: the ignore rule is scoped exactly as the comment claims, nothing tracked under .claude/ is caught by it, and moving worktrees inside the checkout does not disturb any tool this repo runs.

I verified the two things this layout change could plausibly have broken:

  • Scoping: git check-ignore -v .claude/rules/cli.md .claude/settings.json .claude/worktrees/foo/lib/x.dart matches only the third path (.gitignore:45:.claude/worktrees/), and git ls-files .claude still lists all four rule/settings files. git status --porcelain is empty.
  • Tooling: worktrees now living inside the tree could have made dart format . and the analyser walk a nested copy of lib/. Both skip dot-directories. Probe tree with an intentionally unformatted file under .claude/worktrees/slug/lib/: dart format --output=none --set-exit-if-changed . reported Formatted 1 file (0 changed), i.e. the outer file only.
  • Packaging: .worktreeinclude is a root dotfile, so it does not reach the published tarball. flutter pub publish --dry-run lists 34 files and it is not among them, despite .pubignore not naming it.

Minor

.gitignore:45 — worktrees are now both gitignored and inside the working tree, so git clean -xdf in the main checkout deletes a live worktree's contents while .git/worktrees/<slug> survives, leaving a stale registration that needs git worktree prune. Nothing today runs git clean; worth knowing before anything does. (maintainability)

.worktreeinclude:13 — the absolute-path requirement is documented only inside this file, and grep -rn worktree across *.md, *.yaml and *.json returns nothing, so CLAUDE.md and doc/ say nothing about it. A contributor whose local pubspec_overrides.yaml still uses ../magic (correct under the old sibling layout) now gets a worktree where version solving fails on the first path dependency, with no pointer from the file they actually read. One line in CLAUDE.md would close that.

Tests

Nothing covers this, and nothing can: the diff is two ignore-style files with no Dart, YAML or lib/ content, as the description says. Worth noting for anyone reading the green suite below: pubspec_overrides.yaml is absent from this checkout, so those 85 tests ran against the published siblings, which is precisely the silent resolution this PR sets out to prevent in worktrees.

Checks I ran

  • git check-ignore -v on tracked and untracked .claude/ paths: only .claude/worktrees/** ignored.
  • git ls-files .claude, git status --porcelain: all rules still tracked, tree clean.
  • dart format --output=none --set-exit-if-changed . in a probe tree with an unformatted file under .claude/worktrees/slug/lib/: hidden directory skipped.
  • flutter pub publish --dry-run: .worktreeinclude absent from the file list.
  • flutter analyze --no-fatal-infos: No issues found! (ran in 15.1s).
  • dart format --output=none --set-exit-if-changed .: Formatted 26 files (0 changed), exit 0.
  • flutter test: +85: All tests passed!.

@anilcancakir

Copy link
Copy Markdown
Contributor Author

Both fixed.

git clean -xdf is now in the .gitignore comment, including the part that makes it worse than it looks: a worktree's .git is a file rather than a directory, so the nested-repo guard that normally demands -ff does not fire. Applied to every repository in this sweep, not just this one.

The absolute-path requirement is now a Key Gotchas row here, since that is where this repo keeps its traps, rather than in a file a contributor has no reason to open.

Your format probe is the one I would have wanted and did not run: dart format --output=none --set-exit-if-changed . reporting one file over a deliberately unformatted probe under .claude/worktrees/slug/lib/ settles the double-analysis question for the whole sweep.

@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

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

The two new commits are documentation only and both answer my earlier minors; the CLAUDE.md gotcha closes the absolute-path gap cleanly, but the git clean comment records a claim that does not hold - and the fault for that is mine, not the author's.

Since 80ab88c: CLAUDE.md gained the "Relative paths in pubspec_overrides.yaml" row in Key Gotchas, and .gitignore gained four comment lines about git clean. No Dart, YAML or lib/ content, so the earlier analyze/format/test results still stand unchanged.

Minor

.gitignore:44-48 — I raised the git clean -xdf risk last time and I had it wrong, so the comment now asserts something false. Git's nested-repo guard does stop it, .git-as-a-file notwithstanding: on a probe repo with a worktree at .claude/worktrees/slug, git clean -xdf printed Skipping repository .claude/worktrees/slug and the directory survived intact. Only double force reaches it - git clean -xdffn reported Would remove .claude/. The paragraph is worth keeping if the trigger is corrected to -xdff (or -ffdx); as written it will send someone hunting a stale registration that a single -f never creates. (maintainability, git 2.43.0)

.gitignore:43 — stray # at the end of # under it are part of the repository.#, where a bare separator line was clearly intended. Harmless inside a comment, one character to drop.

Tests

Unchanged and unchangeable: the incremental diff is a markdown table row and .gitignore comments. The new table row renders correctly - all ten rows carry three pipes.

Checks I ran

  • git check-ignore -v on .claude/rules/cli.md, .claude/settings.json, .claude/worktrees/foo/lib/x.dart: only the third matches, now at .gitignore:49 after the added comment lines. git ls-files .claude still lists all five tracked files; git status --porcelain empty.
  • Probe repo, worktree at .claude/worktrees/slug: git clean -xdfSkipping repository .claude/worktrees/slug, directory still present. git clean -xdffnWould remove .claude/.
  • sed/awk over CLAUDE.md:92-102: table column count consistent.
  • Did not re-run analyze, format or the suite - nothing executable changed since the commit where I last ran them green.

@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 closed by the two new commits; the only thing left is a stray character in the comment you added.

Since my last review: 3d32f7b documents the git clean -xdf hazard (including the .git-is-a-file detail that defeats the nested-repo guard) in the .gitignore comment, and 3c55c98 adds the absolute-path requirement as a Key Gotchas row in CLAUDE.md. Nothing else changed - no Dart, YAML or lib/ file is touched.

Minor

.gitignore:43 — the new block appends a # to the end of the previous line: # under it are part of the repository.#. Harmless, since the line is already a comment and the pattern on line 49 is untouched, but it reads as a paste artefact. (maintainability)

Tests

Unchanged from last time: nothing covers this and nothing can - the diff is a comment block and a documentation table row.

Checks I ran

  • git check-ignore -v .claude/rules/cli.md .claude/settings.json .claude/worktrees/foo/lib/x.dart: only the third path matches, via .gitignore:49:.claude/worktrees/. The reworded comment did not change the scoping.
  • git status --porcelain: empty.
  • grep -n worktree CLAUDE.md: the gotcha row is present at line 102.
  • No analyze/format/test run this round - the incremental diff contains no 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.

One commit since 3c55c98, and it closes both of my remaining minors: the git clean paragraph now matches what git actually does, and the stray # is gone. Nothing left to raise.

52dc215 rewrites the comment block in .gitignore only. The corrected text - -xdf skips via the nested-repository guard (a gitfile is resolved too), -xdff does not and leaves a registration for git worktree prune - reproduces exactly on a probe repo with a worktree at .claude/worktrees/slug: git clean -xdf printed Skipping repository .claude/worktrees/slug and both files survived; git clean -xdffn printed Would remove .claude/. git 2.43.0.

Tests

Unchanged and still unchangeable: the incremental diff is seven comment lines. No Dart, YAML or lib/ content, so the analyze/format/suite results from earlier in this PR stand.

Checks I ran

  • Probe repo, worktree at .claude/worktrees/slug with an untracked file: git clean -xdfSkipping repository .claude/worktrees/slug, tree intact. git clean -xdffnWould remove .claude/.
  • git check-ignore -v .claude/rules/cli.md .claude/settings.json .claude/worktrees/foo/lib/x.dart: only the third matches, now via .gitignore:51 after the two extra comment lines. Scoping unaffected by the reword.
  • git status --porcelain: empty.
  • No analyze/format/test this round - the incremental diff contains no executable content.

@anilcancakir
anilcancakir merged commit 4ab8153 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