Bug: Worktree creation button uses active-session cwd instead of repo toplevel, and manual path entry silently aborts on ENOENT
Summary
Two related bugs in the mobile app's worktree-creation flow on Happy CLI v1.1.8:
-
Nesting bug: When the user invokes "+ new worktree" from a session whose cwd is itself a worktree, the new worktree is created nested under that worktree instead of at the repo toplevel. The on-disk result is a confusing repo/.dev/worktree/<active>/.dev/worktree/<new> path. Git accepts it (all worktrees share .git) but the path is junk from a UX standpoint.
-
Manual path silent-abort: When the user types a path manually for a new repo, the phone pre-probes the path with git worktree list --porcelain. If the path doesn't exist, the daemon returns spawn /bin/sh ENOENT and the phone bails silently. The daemon's existing requestToApproveDirectoryCreation (HTTP 409 → actionRequired: "CREATE_DIRECTORY") flow is never used.
Environment
- Happy CLI: 1.1.8
- Daemon mode:
start-sync, healthy
- OS: Linux (Ubuntu, kernel 6.17)
- Daemon log:
~/.happy/logs/2026-06-14-07-35-34-pid-265297-daemon.log
Reproduction — Nesting bug
- From the mobile app, open a session whose cwd is an existing worktree (e.g.
01_Server/.dev/worktree/bold-ocean).
- Tap "+ new worktree from selected repo".
- The phone fires (in order):
[11:03:09.781] Shell command request: git worktree add -b eager-star .dev/worktree/eager-star
[11:03:09.962] Shell command result: { success: true, exitCode: 0, ... }
[11:03:10.234] [API MACHINE] Received RPC: spawn-happy-session
[11:03:10.236] params: {
"directory": "/home/michael/01_Server/.dev/worktree/bold-ocean/.dev/worktree/eager-star",
"agent": "claude"
}
[11:03:10.254] [SPAWN HAPPY CLI] Spawning: happy claude ... in
/home/michael/01_Server/.dev/worktree/bold-ocean/.dev/worktree/eager-star
Expected on-disk location: 01_Server/.dev/worktree/eager-star
Actual: 01_Server/.dev/worktree/bold-ocean/.dev/worktree/eager-star
git worktree list confirms:
/home/michael/01_Server/.dev/worktree/bold-ocean/.dev/worktree/eager-star eb7b4fd [eager-star]
For contrast, when invoked from a session whose cwd is the main repo toplevel the path is correct:
[11:03:56.886] Shell command request: git worktree add -b smooth-mountain .dev/worktree/smooth-mountain
[11:03:57.317] Spawning: ... directory: /home/michael/01_Server/.dev/worktree/smooth-mountain
Reproduction — Manual path silent-abort
- From the new-session screen, type a repo path that does not yet exist on disk.
- Phone fires
git worktree list --porcelain with cwd = <typed path>.
- Daemon returns:
[11:06:13.155] Shell command failed: {
success: false,
exitCode: 1,
error: 'spawn /bin/sh ENOENT',
stdoutLen: 0, stderrLen: 20
}
(Repeats while the user keeps typing.)
4. Phone never advances to spawn-happy-session, never shows a "create directory?" prompt. UI just appears unresponsive.
The daemon already supports a clean create-on-spawn path: spawn-happy-session with approvedNewDirectoryCreation: false returns HTTP 409 + actionRequired: "CREATE_DIRECTORY", expecting the client to prompt and re-send with approvedNewDirectoryCreation: true. That flow is bypassed here.
Root cause
The phone uses the active session's cwd as the base for the git worktree add shell command. For sessions whose cwd is itself a worktree, the relative path .dev/worktree/<new> resolves under that worktree instead of the repo toplevel.
For manual-path entry, the phone's pre-flight git worktree list --porcelain probe fails ENOENT on non-existent dirs and there's no fallback to the daemon's existing create-approval flow.
Suggested fix
Nesting bug
Before sending the git worktree add shell command, resolve the repo toplevel:
git -C <selected-session-cwd> rev-parse --show-toplevel
# or, to also handle the worktree case explicitly:
git -C <selected-session-cwd> rev-parse --path-format=absolute --git-common-dir
# common-dir's parent is the main checkout
Then either:
cd <toplevel> for the git worktree add (cwd = toplevel)
- or pass an absolute path:
git worktree add -b <name> <toplevel>/.dev/worktree/<name>
Worktrees should live as siblings at the repo toplevel, never nested. Git tracks them flat under .git/worktrees/ regardless of disk layout; nesting only confuses humans and tools.
Manual path silent-abort
Skip the pre-flight git worktree list --porcelain probe (or treat ENOENT as "path doesn't exist yet" instead of error). Then go straight to spawn-happy-session with approvedNewDirectoryCreation: false and surface the daemon's HTTP 409 response as a "Create dir + new worktree?" UI prompt. The daemon already does the mkdir -p on confirmation.
Bonus UX
Show the resolved target path in the UI before confirming, so the user sees 01_Server/.dev/worktree/eager-river rather than just a name. Currently the target is invisible until the session opens at a possibly-surprising path.
Branch source (open question)
Currently git worktree add -b <name> <path> with no source branches from the cwd's HEAD. This is reasonable when invoked from a feature worktree (continue the branch). When invoked from main, branches from main. Worth surfacing in UI; or default to origin/<default-branch> matching the Claude Agent SDK's worktree.branchFrom: "fresh".
Files referenced
- Daemon RPC spawn-session:
dist/index-q9G4ktSK.mjs:4102-4161
- Daemon spawnSession impl (mkdir + approval flow):
dist/index-q9G4ktSK.mjs:5214-5253
- Daemon bash RPC handler:
dist/types-CIyZti-h.mjs:872-939
- Reproduction logs:
~/.happy/logs/2026-06-14-07-35-34-pid-265297-daemon.log (lines 11:03:09 — 11:06:16)
Bug: Worktree creation button uses active-session cwd instead of repo toplevel, and manual path entry silently aborts on ENOENT
Summary
Two related bugs in the mobile app's worktree-creation flow on Happy CLI v1.1.8:
Nesting bug: When the user invokes "+ new worktree" from a session whose cwd is itself a worktree, the new worktree is created nested under that worktree instead of at the repo toplevel. The on-disk result is a confusing
repo/.dev/worktree/<active>/.dev/worktree/<new>path. Git accepts it (all worktrees share.git) but the path is junk from a UX standpoint.Manual path silent-abort: When the user types a path manually for a new repo, the phone pre-probes the path with
git worktree list --porcelain. If the path doesn't exist, the daemon returnsspawn /bin/sh ENOENTand the phone bails silently. The daemon's existingrequestToApproveDirectoryCreation(HTTP 409 →actionRequired: "CREATE_DIRECTORY") flow is never used.Environment
start-sync, healthy~/.happy/logs/2026-06-14-07-35-34-pid-265297-daemon.logReproduction — Nesting bug
01_Server/.dev/worktree/bold-ocean).Expected on-disk location:
01_Server/.dev/worktree/eager-starActual:
01_Server/.dev/worktree/bold-ocean/.dev/worktree/eager-stargit worktree listconfirms:For contrast, when invoked from a session whose cwd is the main repo toplevel the path is correct:
Reproduction — Manual path silent-abort
git worktree list --porcelainwithcwd = <typed path>.(Repeats while the user keeps typing.)
4. Phone never advances to
spawn-happy-session, never shows a "create directory?" prompt. UI just appears unresponsive.The daemon already supports a clean create-on-spawn path:
spawn-happy-sessionwithapprovedNewDirectoryCreation: falsereturns HTTP 409 +actionRequired: "CREATE_DIRECTORY", expecting the client to prompt and re-send withapprovedNewDirectoryCreation: true. That flow is bypassed here.Root cause
The phone uses the active session's cwd as the base for the
git worktree addshell command. For sessions whose cwd is itself a worktree, the relative path.dev/worktree/<new>resolves under that worktree instead of the repo toplevel.For manual-path entry, the phone's pre-flight
git worktree list --porcelainprobe fails ENOENT on non-existent dirs and there's no fallback to the daemon's existing create-approval flow.Suggested fix
Nesting bug
Before sending the
git worktree addshell command, resolve the repo toplevel:Then either:
cd <toplevel>for thegit worktree add(cwd = toplevel)git worktree add -b <name> <toplevel>/.dev/worktree/<name>Worktrees should live as siblings at the repo toplevel, never nested. Git tracks them flat under
.git/worktrees/regardless of disk layout; nesting only confuses humans and tools.Manual path silent-abort
Skip the pre-flight
git worktree list --porcelainprobe (or treat ENOENT as "path doesn't exist yet" instead of error). Then go straight tospawn-happy-sessionwithapprovedNewDirectoryCreation: falseand surface the daemon's HTTP 409 response as a "Create dir + new worktree?" UI prompt. The daemon already does themkdir -pon confirmation.Bonus UX
Show the resolved target path in the UI before confirming, so the user sees
01_Server/.dev/worktree/eager-riverrather than just a name. Currently the target is invisible until the session opens at a possibly-surprising path.Branch source (open question)
Currently
git worktree add -b <name> <path>with no source branches from the cwd's HEAD. This is reasonable when invoked from a feature worktree (continue the branch). When invoked from main, branches from main. Worth surfacing in UI; or default toorigin/<default-branch>matching the Claude Agent SDK'sworktree.branchFrom: "fresh".Files referenced
dist/index-q9G4ktSK.mjs:4102-4161dist/index-q9G4ktSK.mjs:5214-5253dist/types-CIyZti-h.mjs:872-939~/.happy/logs/2026-06-14-07-35-34-pid-265297-daemon.log(lines 11:03:09 — 11:06:16)