From e3656a1ec3dc5a18b0c33962468a1902065d4496 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Thu, 3 Sep 2026 19:27:48 +0300 Subject: [PATCH 1/3] chore(worktree): put worktrees under .claude and carry the overrides into them Worktrees used to sit beside the checkout as -, 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/. Pattern proven on fluttersdk/magic_starter#124. --- .gitignore | 5 +++++ .worktreeinclude | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 .worktreeinclude diff --git a/.gitignore b/.gitignore index 564befb..cce9964 100644 --- a/.gitignore +++ b/.gitignore @@ -42,5 +42,10 @@ migrate_working_dir/ CLAUDE.local.md .sync-agents-cache.json +# Worktrees Claude Code creates for parallel sessions and isolated subagents. +# Only this subdirectory: the rest of `.claude/` is tracked, because the rules +# under it are part of the repository. +.claude/worktrees/ + # Local sibling-package path wiring (dev only) pubspec_overrides.yaml diff --git a/.worktreeinclude b/.worktreeinclude new file mode 100644 index 0000000..1c9f3cf --- /dev/null +++ b/.worktreeinclude @@ -0,0 +1,21 @@ +# Gitignored files copied into every worktree Claude Code creates, for `--worktree`, +# for `EnterWorktree`, and for a subagent with `isolation: worktree`. +# +# A worktree is a fresh checkout, so `pubspec_overrides.yaml` is absent from it, and +# its absence fails silently rather than loudly: the siblings resolve from pub.dev +# instead of the working trees beside this one, `flutter pub get` succeeds, and the +# suite then passes against the PUBLISHED packages while the diff under review is of +# the local ones. An unreleased sibling API is where that bites. +# +# `.gitignore` syntax, and only files that match AND are gitignored are copied, so a +# tracked file can never be duplicated by this list. +# +# The paths inside `pubspec_overrides.yaml` have to be ABSOLUTE for this to work. +# Worktrees live under `.claude/worktrees/`, so a relative `../magic` resolves +# to `.claude/worktrees/magic`, which does not exist, and version solving fails on +# the first path dependency. +# +# NOT processed when a WorktreeCreate hook replaces the default git logic; such a +# hook has to copy these itself. + +pubspec_overrides.yaml From 3850557cb7309716aa577d3ec4f0731b50205221 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Thu, 3 Sep 2026 20:01:30 +0300 Subject: [PATCH 2/3] docs(worktree): record the session-ownership guard this layout retires --- .gitignore | 6 +++++- .worktreeinclude | 9 +++++++++ doc/commands/dusk-doctor.md | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cce9964..e9992d3 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,11 @@ CLAUDE.local.md # Worktrees Claude Code creates for parallel sessions and isolated subagents. # Only this subdirectory: the rest of `.claude/` is tracked, because the rules -# under it are part of the repository. +# under it are part of the repository.# +# They sit inside the working tree rather than beside it, so `git clean -xdf` here +# wipes a live worktree's contents while `.git/worktrees/` survives, leaving a +# registration that needs `git worktree prune`. A worktree's `.git` is a file rather +# than a directory, so git's nested-repo guard does not stop it. .claude/worktrees/ # Local sibling-package path wiring (dev only) diff --git a/.worktreeinclude b/.worktreeinclude index 1c9f3cf..2ffd897 100644 --- a/.worktreeinclude +++ b/.worktreeinclude @@ -15,6 +15,15 @@ # to `.claude/worktrees/magic`, which does not exist, and version solving fails on # the first path dependency. # +# One thing this layout costs, which no file here can fix because the rule lives +# upstream in artisan: `sessionOwnershipError` treats a working directory INSIDE +# `projectRoot` as owning the session (`state_file.dart:262`, `_isWithin` at `:282`), +# and a worktree now sits under the main checkout. So a `dusk:*` call from a worktree +# that has no session of its own falls back to the main checkout's app, drives THAT, +# and reports success. The old sibling layout tripped the guard instead. Run +# `artisan start` in the worktree, or pass `--state=`, before any dusk command +# there. +# # NOT processed when a WorktreeCreate hook replaces the default git logic; such a # hook has to copy these itself. diff --git a/doc/commands/dusk-doctor.md b/doc/commands/dusk-doctor.md index 701fdb8..4619872 100644 --- a/doc/commands/dusk-doctor.md +++ b/doc/commands/dusk-doctor.md @@ -100,6 +100,8 @@ Compares `state.json`'s `projectRoot` against the directory the command is runni WARN, naming both paths. Skipped when no `projectRoot` is recorded. Paths are compared after resolving symlinks, so a worktree checkout does not read as a mismatch on the same directory. +That last property cuts the other way now that worktrees live at `/.claude/worktrees/` rather than beside the checkout. A worktree is INSIDE `projectRoot`, so the check reads it as the owner and stays silent, while a `dusk:*` call from a worktree with no session of its own drives the app started from the main checkout and succeeds. The layout that used to trip this check no longer exists, so the check no longer covers the case it was written for. Start a session in the worktree, or pass `--state=`. + ### 7. CDP session health Probes the `cdpPort` recorded in state for three failures. They matter together because they present as one symptom, a capture that never changes, and nothing else in the output distinguishes them: From 424d0395df2b8edebbd4b603a60ba653fcbc3d6a Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Thu, 3 Sep 2026 20:30:46 +0300 Subject: [PATCH 3/3] fix(worktree): git clean -xdf skips a worktree, only -xdff does not 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. --- .gitignore | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index e9992d3..5fdc487 100644 --- a/.gitignore +++ b/.gitignore @@ -44,11 +44,13 @@ CLAUDE.local.md # Worktrees Claude Code creates for parallel sessions and isolated subagents. # Only this subdirectory: the rest of `.claude/` is tracked, because the rules -# under it are part of the repository.# -# They sit inside the working tree rather than beside it, so `git clean -xdf` here -# wipes a live worktree's contents while `.git/worktrees/` survives, leaving a -# registration that needs `git worktree prune`. A worktree's `.git` is a file rather -# than a directory, so git's nested-repo guard does not stop it. +# under it are part of the repository. +# +# They sit inside the working tree rather than beside it. `git clean -xdf` leaves them +# alone: git's nested-repository guard resolves a gitfile too, so it reports "Skipping +# repository .claude/worktrees/" and the contents survive. `git clean -xdff` does +# not skip them, and wipes a live worktree while `.git/worktrees/` survives, +# leaving a registration that `git worktree prune` then has to clear. .claude/worktrees/ # Local sibling-package path wiring (dev only)