diff --git a/src/main/ipc/repos/proven-absence.test.ts b/src/main/ipc/repos/proven-absence.test.ts index f0d377cb4f8..18335fdadb7 100644 --- a/src/main/ipc/repos/proven-absence.test.ts +++ b/src/main/ipc/repos/proven-absence.test.ts @@ -111,4 +111,16 @@ describe('proven-absence', () => { }) expect(() => describeError(e)).not.toThrow() }) + + it('treats underscore errno families like EAI_* as authoritative', () => { + // EAI_AGAIN is a real libuv code; missing it let a transient DNS failure whose message + // quotes ENOENT read as proven absence. + for (const code of ['EAI_AGAIN', 'EAI_NONAME']) { + expect( + isProvenAbsent( + Object.assign(new Error("ENOENT: no such file or directory, stat '/x'"), { code }) + ) + ).toBe(false) + } + }) }) diff --git a/src/main/ipc/repos/proven-absence.ts b/src/main/ipc/repos/proven-absence.ts index 36daa6f6b5d..247c118d6b1 100644 --- a/src/main/ipc/repos/proven-absence.ts +++ b/src/main/ipc/repos/proven-absence.ts @@ -7,9 +7,14 @@ const ENOTDIR_MESSAGE = /^ENOTDIR: not a directory\b/ // Why shape rather than a fixed list: EVERY real errno is authoritative, not just the two we care // about — an EACCES or ELOOP is a definitive non-absence answer even when the message quotes -// ENOENT. But a wrapper attaching a domain string (`REMOTE_FS_ERROR`) is not an errno and must not -// suppress the message fallback, which is the only classification path over the SSH relay. -const ERRNO_NAME = /^E[A-Z0-9]+$/ +// ENOENT. Underscores are required: `EAI_AGAIN`/`EAI_NONAME` are real libuv codes, and missing them +// let a transient DNS failure fall through to the message and read as proven absence. +// +// An E-prefixed code we do not recognise is therefore treated as an errno, i.e. NOT absence. That +// biases toward refusing, which is the safe direction for this helper. Only a code that is not +// errno-shaped at all (`REMOTE_FS_ERROR`, or the relay's numeric -32000) falls through to the +// message — the only classification path that survives the SSH relay. +const ERRNO_NAME = /^E[A-Z0-9_]+$/ /** Whether a failed probe proves the path is absent. Never throws. */ export function isProvenAbsent(error: unknown): boolean { diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index db2651c8270..18902a58cdb 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -1278,7 +1278,7 @@ import { LEFTOVER_GIT_DIR_RETRY_HINT, repositoryCheckUnavailableError } from '../ipc/repos/repository-creation-messages' -import { isProvenAbsent } from '../ipc/repos/proven-absence' +import { describeError, isProvenAbsent } from '../ipc/repos/proven-absence' import { getWorktreeWatcherRemoval } from '../ipc/worktree-watcher-removal' import { acquireWatcherRemovalGate } from '../ipc/watcher-removal-gate' import { @@ -23925,7 +23925,7 @@ export class OrcaRuntimeService { // as "no failure" and fail open into `git init`. if (!isProvenAbsent(error)) { gitProbeFailed = true - gitProbeFailure = error instanceof Error ? error.message : String(error) + gitProbeFailure = describeError(error) } return null })