Conversation
…edium] GitHub repo Sessions failed to start on Macs launched from the Dock. The credential broker and token prefetch both succeeded, but `credential.helper` was `!node "<helper.cjs>"` — a PATH lookup. A GUI-launched app inherits the Dock's minimal PATH, which usually has no `node`, so the helper never ran, git found no username under `GIT_TERMINAL_PROMPT=0`, and the clone aborted with `terminal prompts disabled` → `turn_pre_prompt_failed`. Build the host helper command from `process.execPath` instead, quoting both words (installation paths contain spaces) and normalizing Windows separators to `/`, since git runs the `!` form through its bundled MinGW bash. The diagnostic probe spawns the same runtime rather than a bare `node`: on an affected machine it reported a spawn error instead of the broker verdict, and on a machine with a PATH `node` it would have succeeded against a runtime git never used. Host git children, the probe, and the ACP session environment now carry `ELECTRON_RUN_AS_NODE=1` when the CLI is the Electron binary. Container helpers keep `node`, which is on PATH inside the image. Model: claude-opus-5
zxch3n
left a comment
There was a problem hiding this comment.
Codex review (could not --approve: GitHub rejects self-approval by zxch3n, who is also the PR author).
Reviewed PR #644 at 89d80c1 against origin/main. No blocking correctness or security findings. The host helper now uses process.execPath with both words quoted and Windows separators normalized to /; host Git, the ACP environment, and diagnostics propagate ELECTRON_RUN_AS_NODE=1 for Electron; container helpers retain !node.
Focused Vitest could not run in the review checkout because node_modules/vitest is absent; git diff --check passed.
Non-blocking test-quality notes: the WorktreeManager Electron flag test seeds process.env, which the old runGit already inherited, and worktree-manager.create only tests config-arg pass-through; neither affects runtime correctness.
PR is still draft; this review does not mark it ready.
Add the Chinese counterpart of the CLI-runtime credential helper note and mark both sides `Translation: current` with counterpart links. Same decision, not a rewrite; no code or Spec changes. Model: claude-opus-5
…low] The helper-path swap does not need dedicated coverage: delete the new `git-credential-helper-script.test.ts` and revert the expansions in `worktree-manager-broker-auth.test.ts` and `session-manager.test.ts`. The only remaining test edit updates the existing `!node "…helper.cjs"` literal in `worktree-manager.create.test.ts` so the suite still passes. `formatCredentialHelperCommand` and the injectable env/platform parameters existed only for those tests, so they fold back into the two callers. Both notes are cut to the problem and the fix. Model: claude-opus-5
Related issue
(same-repository branch; no intake Issue)
Problem / pressure
GitHub repo Session startup fails on Macs where the desktop was launched from the Dock. The credential broker starts and the token prefetch succeeds, but the bare clone aborts:
surfacing to the user as
turn_pre_prompt_failed.The cause is the helper command itself.
buildCredentialHelperValueForHostproduced!node "<helper.cjs>"— a PATH lookup. A GUI-launched app inherits the Dock's minimal PATH, which usually has nonodeat all, so git could not start the helper, found no username underGIT_TERMINAL_PROMPT=0, and gave up. The credential chain was healthy the whole time; only the runtime resolution was broken.The diagnostic path had the same bug (
spawn('node', …)), so on an affected machine the probe reported a spawn error instead of the broker verdict — and on a machine that happens to have a PATHnode, it would have succeeded against a runtime git never used, hiding the defect being diagnosed.Every other CLI child — CLI/MCP, adapters, the watch worker — already resolves
process.execPath+ELECTRON_RUN_AS_NODE. The git credential helper was the last one depending on ambient PATH.Summary
git-credential-helper-script.ts: newformatCredentialHelperCommand(nodePath, helperPath, platform). The host value is now!"<execPath>" "<helper.cjs>"; both words are quoted (installation paths contain spaces:Lody Helper,Program Files) and onwin32backslashes become forward slashes, because git runs the!form through its bundled MinGW bash where\escapes rather than separates.buildCredentialHelperRuntimeEnv()returns{ ELECTRON_RUN_AS_NODE: '1' }when the CLI is the Electron binary. Applied toWorktreeManager.runGit, the helper probe env, and the ACP session env insession-manager.ts.runCredentialHelperProbespawnsprocess.execPath, nevernode.!node "…":nodeis on PATH inside the image and the hostexecPathdoes not exist there.~/.lody/bin, and no login-shell PATH resolution is added.Visual explanation
flowchart LR subgraph before["before — PATH lookup"] G1["git clone --bare"] --> H1["credential.helper<br/>!node "helper.cjs""] H1 -. "ENOENT: no node on GUI PATH" .-> F1(["could not read Username<br/>→ turn_pre_prompt_failed"]) end subgraph after["after — CLI runtime"] G2["git clone --bare<br/>ELECTRON_RUN_AS_NODE=1"] --> H2["credential.helper<br/>!"<execPath>" "helper.cjs""] H2 --> B2["broker /git-credential"] --> OK(["username + password"]) endThree call sites resolve the runtime, all through one module:
Before / after
credential.helper=!node "/Users/me/.lody/repos/<id>/lody-git-credential-helper.cjs"credential.helper=!"/Applications/Lody.app/Contents/MacOS/Lody" "/Users/me/.lody/repos/<id>/lody-git-credential-helper.cjs"spawn('node', [helperPath, 'get'])→ probe reports ENOENT, broker verdict lostspawn(process.execPath, [helperPath, 'get'])→ probe reports what git would see!node "C:\Users\dev\…\helper.cjs"(backslashes reach MinGW bash)!"C:/Program Files/Lody/Lody.exe" "C:/Users/dev/…/helper.cjs"ELECTRON_RUN_AS_NODE1on the git child, the probe, and the ACP session env under ElectronTest plan
apps/cli/src/lib/git-credential-helper-script.test.tsruns realgit credential fillagainst the produced helper value, with the helper under a directory containing a space and a failingnodeshim prepended to PATH — the Dock environment, without depending on the machine's real PATH. Reverting the fix reproduces the exact production error:fatal: could not read Username for 'https://…': terminal prompts disabled. Skipped onwin32(POSIX shim); the Windows separator rule is covered by formatting assertions instead.worktree-manager-broker-auth.test.ts(owning suite, extended): host git argv namesprocess.execPathand never!node, the probe spawnsprocess.execPathwith[helperPath, 'get'], andELECTRON_RUN_AS_NODEis forced under Electron / absent under plain Node. ItsmakeChildfake now emitscloseonly after both streams drain, so a stderr-classified failure is actually visible to the code under test.session-manager.test.ts(extended):GIT_CONFIG_VALUE_1andELECTRON_RUN_AS_NODEon the ACP session env, with a stand-in token manager and broker.tests/worktree-manager.create.test.ts: updated the hardcoded helper literal to the quoted form.GIT_CONFIG_VALUE_1/argv assertions are written independently of the formatter so they are not tautological).pnpm --filter lody typecheck,pnpm format,pnpm run docs check(exit 0) all pass. Fullpnpm check: 2721 passed, 1 failed —tests/worktree-gc.test.tsfails identically on this branch's merge-base (macOS/private/varvs/varrealpath), unrelated to this change.Context handoff
Instructions for reviewing agents
git-credential-helper-script.tsquoting/normalization, and that all threeELECTRON_RUN_AS_NODEcall sites (hostrunGit, probe env, ACP session env) actually reach the child process.ELECTRON_RUN_AS_NODEafter the caller-suppliedenvspread inrunGit(a caller can no longer unset it); keeping the container helper on!node; normalizing\→/only onwin32rather than unconditionally.git credential fill; and a WindowsexecPathcontaining a"or a UNC prefix is escaped but untested.Authoring context
~/.lody/bin/nodesymlink, no login-shell PATH resolution; no changes to broker routing, token handling, or the private pointer.credential.helperis a shell command git executes, so its quoting is security-relevant; both words are quoted and embedded"escaped. ForcingELECTRON_RUN_AS_NODEoverrides a caller-supplied value on git children.git credential fillrun; no packaged-desktop end-to-end run; the container helper value is deliberately untouched.🤖 Generated with Claude Code