From efaad5bba139ff1471e6e7cc6d8d09f9ba390c9a Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:04:44 -0700 Subject: [PATCH] Gate agent startup on the effective PTY cwd after default fallback Co-authored-by: Orca --- src/main/providers/local-pty-provider.test.ts | 12 +++++++----- src/main/providers/local-pty-provider.ts | 9 ++++++--- src/main/providers/pty-default-cwd.ts | 6 ++++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/providers/local-pty-provider.test.ts b/src/main/providers/local-pty-provider.test.ts index 4f738369217..e8fe9143964 100644 --- a/src/main/providers/local-pty-provider.test.ts +++ b/src/main/providers/local-pty-provider.test.ts @@ -82,6 +82,7 @@ vi.mock('../wsl', () => ({ })) import { LocalPtyProvider } from './local-pty-provider' +import { isRootLikePath } from './pty-path-safety' import { POWERLEVEL10K_WIZARD_DISABLE_ENV } from '../pty/powerlevel10k-wizard-env' describe('LocalPtyProvider', () => { @@ -228,14 +229,15 @@ describe('LocalPtyProvider', () => { ) }) - it('rejects automatic agent startup without an explicit cwd', async () => { + it('falls back to the safe default cwd for automatic agent startup without an explicit cwd', async () => { spawnMock.mockClear() - await expect(provider.spawn({ cols: 80, rows: 24, command: 'codex' })).rejects.toThrow( - /requires a non-root workspace/ - ) + // Why: an omitted cwd resolves to a guaranteed-safe default home (the + // guard only rejects root-like paths), so the agent must still launch. + await expect(provider.spawn({ cols: 80, rows: 24, command: 'codex' })).resolves.toBeDefined() - expect(spawnMock).not.toHaveBeenCalled() + const spawnCall = spawnMock.mock.calls.at(-1)! + expect(isRootLikePath(spawnCall[2].cwd)).toBe(false) }) it('rejects automatic agent startup at POSIX root', async () => { diff --git a/src/main/providers/local-pty-provider.ts b/src/main/providers/local-pty-provider.ts index 13803ae15a9..0d2eb19f1a6 100644 --- a/src/main/providers/local-pty-provider.ts +++ b/src/main/providers/local-pty-provider.ts @@ -343,12 +343,15 @@ export class LocalPtyProvider implements IPtyProvider { const startupAgentRecognition = args.command ? recognizeAgentProcessFromCommandLine(args.command) : null - if (args.command && startupAgentRecognition) { - assertSafeAgentStartupCwd(args.cwd, args.command) - } const defaultCwd = getDefaultCwd() const cwd = args.cwd || defaultCwd + // Why: gate on the effective cwd (post default-cwd fallback), not the raw + // args.cwd — an omitted cwd resolves to a safe default and must not be + // rejected as if it were a root-like path. + if (args.command && startupAgentRecognition) { + assertSafeAgentStartupCwd(cwd, args.command) + } const wslInfo = process.platform === 'win32' ? parseWslPath(cwd) : null const worktreeWslContext = process.platform === 'win32' ? getWslContextFromWorktreeId(args.worktreeId) : undefined diff --git a/src/main/providers/pty-default-cwd.ts b/src/main/providers/pty-default-cwd.ts index 463aee7d3ec..e9a7b7a5a3b 100644 --- a/src/main/providers/pty-default-cwd.ts +++ b/src/main/providers/pty-default-cwd.ts @@ -22,6 +22,12 @@ export function resolveSafePtyDefaultCwd(env: NodeJS.ProcessEnv = process.env): // Why: silently falling back to "/" or a drive root is the exact runaway-CPU // bug this module prevents (see runaway-cpu-hidden-usage-pty-design.md) — fail loud. if (!selected) { + // Log the rejected candidates so the rare "no safe home at all" environment + // (e.g. HOME=/ or unset in a minimal container) is diagnosable, not opaque. + console.error( + '[pty-default-cwd] No safe default working directory for terminal launch; every home candidate resolved to a root-like path.', + { platform: process.platform, candidates } + ) throw new Error('No safe default working directory is available for terminal launch.') } return selected