Files
orca/src/shared/terminal-startup-cwd.ts
NeilandOrca 4a6d12959f fix: recover fresh local terminal spawns from a deleted saved cwd (#7847)
After #7750 removed the containment guard, the residual #7239 failure mode
is a persisted/inherited startupCwd whose directory no longer exists: every
spawn dies with the provider's missing-directory error. Fresh local renderer
spawns now opt in (cwdFallback: 'worktree') to recover at the workspace root
with a generic in-terminal notice; reattach, SSH, remote-runtime, runtime/API
and mobile callers keep exact cwd semantics, and existing directories —
including outside the worktree (#7685) — spawn as requested.

Co-authored-by: Orca <help@stably.ai>
2026-07-08 16:33:58 -07:00

83 lines
3.3 KiB
TypeScript

import { FLOATING_TERMINAL_WORKTREE_ID } from './constants'
import { resolveRuntimePath } from './cross-platform-path'
import { parseWorkspaceKey } from './workspace-scope'
import { splitWorktreeIdForFilesystem } from './worktree-id'
export type TerminalStartupCwdMissingDirFallback = {
// Why: only local callers can probe the filesystem — SSH/remote worktree
// paths live on another host — so the existence check is injected.
directoryExists: (path: string) => boolean
onFallbackToWorkspaceRoot?: (missingCwd: string) => void
}
export function resolveTerminalStartupCwd(
worktreePath: string,
requestedCwd?: string | null,
missingDirFallback?: TerminalStartupCwdMissingDirFallback
): string | undefined {
const trimmedCwd = requestedCwd?.trim()
if (!trimmedCwd) {
return undefined
}
// Why: resolve relative requests against the worktree root and normalize
// `..`; the cwd is intentionally not constrained to the worktree, so opening
// or splitting a terminal outside it (e.g. after `cd ..`) is allowed. (#7685)
const resolvedCwd = resolveRuntimePath(worktreePath, trimmedCwd)
if (
missingDirFallback &&
resolvedCwd !== worktreePath &&
!missingDirFallback.directoryExists(resolvedCwd) &&
missingDirFallback.directoryExists(worktreePath)
) {
// Why: a persisted/inherited startup folder can be deleted later; spawning
// into it fails on every retry and bricks terminal creation for that tab
// (#7239), so recover at the workspace root. If the root is missing too
// (unmounted volume, stopped WSL distro), keep the requested cwd so the
// provider surfaces its normal error instead of a misleading fallback.
missingDirFallback.onFallbackToWorkspaceRoot?.(resolvedCwd)
return worktreePath
}
return resolvedCwd
}
export function resolveTerminalStartupCwdForWorkspace(args: {
workspaceId?: string
requestedCwd?: string | null
resolveFolderWorkspacePath?: (folderWorkspaceId: string) => string | null | undefined
missingDirFallback?: TerminalStartupCwdMissingDirFallback
}): string | undefined {
if (!args.requestedCwd || args.requestedCwd.trim().length === 0) {
return undefined
}
if (args.workspaceId === FLOATING_TERMINAL_WORKTREE_ID) {
// Why: floating terminals have no worktree root; their cwd was already
// resolved against the trusted-directory grants in resolveFloatingTerminalCwd.
return args.requestedCwd
}
const workspacePath = resolveTerminalWorkspacePath(
args.workspaceId,
args.resolveFolderWorkspacePath
)
if (!workspacePath) {
// Why: without a worktree root we can't anchor a relative request, so fall
// back to the provider default rather than guessing a base.
return undefined
}
return resolveTerminalStartupCwd(workspacePath, args.requestedCwd, args.missingDirFallback)
}
function resolveTerminalWorkspacePath(
workspaceId: string | undefined,
resolveFolderWorkspacePath: ((folderWorkspaceId: string) => string | null | undefined) | undefined
): string | null {
if (!workspaceId) {
return null
}
const scope = parseWorkspaceKey(workspaceId)
if (scope?.type === 'folder') {
return resolveFolderWorkspacePath?.(scope.folderWorkspaceId) ?? null
}
const worktreeId = scope?.type === 'worktree' ? scope.worktreeId : workspaceId
return splitWorktreeIdForFilesystem(worktreeId)?.worktreePath ?? null
}