Skip to content

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

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#40
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.
@kodizm

kodizm Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

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

Config-only change and correct as written; the one thing worth knowing is that moving worktrees inside the checkout silently disables artisan's session-ownership guard for them.

Major

.gitignore:48 — correctness. sessionOwnershipError (artisan lib/src/state/state_file.dart:262, via _isWithin at :282) treats a working directory inside projectRoot as owning the session. With worktrees now at <repo>/.claude/worktrees/<slug>, a dusk:* call from a worktree that has no session of its own falls back to the global ~/.artisan/state.json, sees the main checkout's projectRoot as a parent, and drives the app started from the main checkout while reporting success. The old sibling layout tripped the guard. Verified against the resolved dependency:

worktree-under-repo => OWNED (no warning)
old sibling layout   => warns

That is exactly the failure dusk:doctor check 6 was written for (lib/src/commands/dusk_doctor_command.dart:542-545 cites "a sibling worktree take the file mid-session"): the doctor now prints Session ownership: state.json describes this project for that case. Nothing in this diff can fix it, since the is-within rule lives upstream, but a worktree session needs artisan start of its own or --state=<path>, and that is worth a line in .worktreeinclude next to the absolute-paths note so the next reader of the file learns it there.

Minor

doc/commands/dusk-doctor.md:101 — the sentence "a worktree checkout does not read as a mismatch on the same directory" now reads as reassurance for the case above, where the mismatch is real and unreported. Worth rewording while the layout change is fresh.

Tests

Nothing to test: no Dart, YAML or lib/ file is touched, and both files are tooling config with no runtime path.

Checks I ran

  • dart pub publish --dry-run -> Package has 0 warnings, 453 KB archive, and .worktreeinclude is absent from the file listing (pub skips dotfiles), so .pubignore needs no companion entry.
  • git check-ignore -v .worktreeinclude -> not ignored, so the new file is tracked as intended, and .pubignore already carries .claude/ so .claude/worktrees/ never reaches the archive.
  • dart run probe of sessionOwnershipError with the repo's package_config.json -> output quoted above.
  • No linter, formatter or test run: the diff contains no code they read.

@anilcancakir

Copy link
Copy Markdown
Contributor Author

The Major is real and I confirmed it against the resolved source rather than the report: sessionOwnershipError (state_file.dart:262) returns null when _isWithin(workingDirectory, recorded) (:282), and .claude/worktrees/<slug> is inside projectRoot, so the guard that the old sibling layout tripped now stays silent.

Nothing here can fix the rule, as you said, so I documented it in both places a reader would hit it:

  • .worktreeinclude now carries a paragraph naming the two file:line anchors and the remedy (artisan start in the worktree, or --state=<path>).
  • doc/commands/dusk-doctor.md check 6: the sentence you flagged stays, since it is still true about symlink resolution, but it is now followed by what that property costs under this layout. The doc said the check covers the case; it no longer does, and saying so plainly seemed better than deleting the sentence.

Also recorded, from another review in this sweep: git clean -xdf at the root now reaches a live worktree, so 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.

The new commit is documentation only and it lands both fixes I asked for; one stray character in a .gitignore comment is all that is left.

Since e3656a1: .worktreeinclude gains the session-ownership paragraph with the two anchors and the remedy, doc/commands/dusk-doctor.md check 6 now says plainly what the symlink-resolution property costs under this layout, and the .gitignore comment records the git clean -xdf hazard. My earlier Major and Minor are answered and I am not restating them.

Minor

.gitignore:46 — maintainability. The comment line ends # under it are part of the repository.#; the trailing # looks like a blank # separator line that got joined onto the sentence above rather than starting the new paragraph. Harmless to git, since the whole line is a comment, but it reads as a typo in a file whose comments are the documentation.

Tests

Nothing to test: the commit touches two tooling-config comment blocks and one Markdown page, with no runtime path.

Checks I ran

  • git check-ignore -v .claude/worktrees/probe/x.txt -> matched .gitignore:52:.claude/worktrees/, and git check-ignore -v .claude/rules/tests.md exited 1, so the rewritten comment block did not disturb the rule: worktrees ignored, the tracked rules under .claude/ still tracked.
  • sed/cat -A over .gitignore:44-56 -> confirmed the stray # above is a literal character on the comment line, not a rendering artefact of the diff.
  • No linter, formatter or test run: this commit contains no code they read.

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 comment-only commit since 3850557, and it is correct: the stray # is gone and the git clean claim now matches what git actually does.

Since 3850557: .gitignore:44-54 fixes the joined # under it are part of the repository.# into a sentence plus a real # separator, and rewrites the hazard paragraph from "git clean -xdf wipes a live worktree" to "-xdf skips it, -xdff does not". I reproduced both halves of the new claim rather than taking it on the diff's word, and it holds. My earlier Minor is answered; the Major from the first review was answered in the thread and is not restated.

No findings.

Tests

Nothing to test: the commit changes only comment lines in .gitignore, with no runtime path.

Checks I ran

  • Throwaway repo with a worktree at .claude/worktrees/probe and an untracked file inside it: git clean -xdf -> Skipping repository .claude/worktrees/probe and live.txt survived; git clean -xdff -> Removing .claude/, contents gone, .git/worktrees/probe still registered. That is the new comment verbatim, including the quoted "Skipping repository" wording and the leftover registration.
  • git check-ignore -v .claude/worktrees/x/y.txt -> matched .gitignore:54, and git check-ignore -v .claude/rules/tests.md exited 1, so the rewritten comment block did not disturb the rule.
  • No linter, formatter or test run: this commit contains no code they read.

@anilcancakir
anilcancakir merged commit 96a929f 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