From 74f14e1fa296f97a4b6e3a02e033d9869bf9a986 Mon Sep 17 00:00:00 2001 From: Matt <174058705+asdf8675309@users.noreply.github.com> Date: Thu, 23 Jul 2026 08:37:25 -0400 Subject: [PATCH] lifeos: stop a --local launch from the main checkout when worktrees are in use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What 'lifeos --local' starts a session wherever you are. If that's a repo's main checkout and the repo uses .claude/worktrees, the whole session runs on whatever branch the root is parked on — usually not what you meant. Easy footgun if you live in worktrees. ## Change One file. New inMainCheckoutWithWorktrees() (exported). It's plain git: when --git-dir and --git-common-dir match, you're in the main checkout, not a worktree; then it just checks for a .claude/worktrees dir. No version-specific flags, so it works on any git. When it's true, --local refuses with a clear message. Set LIFEOS_ALLOW_ROOT=1 to override. ## Why it's safe It only fires in one spot: --local, from a main checkout, in a repo that actually has .claude/worktrees, with the override off. Anything unexpected falls through to false and the launch proceeds — it never blocks you without an escape hatch, and it touches nothing outside --local. Not a git repo? Returns false, no-op. ## Verification Tested on macOS, Bun 1.3.14. | context | inMainCheckoutWithWorktrees() | --local | |---------|-------------------------------|---------| | not a git repo | false | proceeds | | main checkout, no .claude/worktrees | false | proceeds | | linked worktree (git-dir != git-common-dir) | false | proceeds | | main checkout WITH .claude/worktrees | true | refuses unless LIFEOS_ALLOW_ROOT=1 | ## Scope Just lifeos.ts, just the --local path. No new dependency. Nothing changes for non-worktree repos or for normal worktree launches. Co-authored-by: Claude --- LifeOS/install/LIFEOS/TOOLS/lifeos.ts | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/LifeOS/install/LIFEOS/TOOLS/lifeos.ts b/LifeOS/install/LIFEOS/TOOLS/lifeos.ts index 697cdbf0af..57d70fa35e 100755 --- a/LifeOS/install/LIFEOS/TOOLS/lifeos.ts +++ b/LifeOS/install/LIFEOS/TOOLS/lifeos.ts @@ -394,6 +394,26 @@ function cmdWallpaper(args: string[]) { } +// True when the current directory is a git repo's MAIN checkout (not a linked +// worktree) AND the repo uses the .claude/worktrees convention. The main checkout +// has --git-dir == --git-common-dir; a linked worktree's --git-dir points into +// .git/worktrees/, so they differ. Used to stop a --local launch from running +// a whole session on whatever branch the root happens to be parked on. +export function inMainCheckoutWithWorktrees(): boolean { + try { + // No --path-format flag → works on any git version; the two paths stay directly + // comparable (both ".git" in a main checkout, divergent in a linked worktree). + const r = spawnSync(["git", "rev-parse", "--git-dir", "--git-common-dir"]); + if (r.exitCode !== 0) return false; // not a git repo + const [gitDir, commonDir] = r.stdout.toString().trim().split("\n"); + if (!gitDir || gitDir !== commonDir) return false; // linked worktree → fine + const top = spawnSync(["git", "rev-parse", "--show-toplevel"]).stdout.toString().trim(); + return top.length > 0 && existsSync(join(top, ".claude", "worktrees")); + } catch { + return false; + } +} + // ============================================================================ // Commands // ============================================================================ @@ -403,6 +423,17 @@ async function cmdLaunch(options: { mcp?: string; resume?: boolean; resumeId?: s // Algorithm spec is loaded on-demand when Algorithm mode triggers. // (InstantiatePAI.ts is retired — kept for reference only) + // Guard: --local launches Claude in the CURRENT directory. If that's a repo's main + // checkout (not a worktree) and the repo uses worktrees, the whole session would run + // on whatever stale branch the root is parked on. Refuse unless explicitly overridden. + if (options.local && process.env.LIFEOS_ALLOW_ROOT !== "1" && inMainCheckoutWithWorktrees()) { + error( + "You're in the main checkout root, not a worktree.\n" + + " A --local session here runs on whatever branch the root is parked on.\n" + + " cd into a worktree first, or set LIFEOS_ALLOW_ROOT=1 to override.", + ); + } + requireClaudeCli(); displayBanner(); const args = ["claude"];