{"record":{"id":"fcdab59147af6256","repo":"stablyai/orca","slug":"normalizegiterrormessage-error-upstream","errorCode":null,"errorMessage":"normalizeGitErrorMessage(error, 'upstream')","messagePattern":"normalizeGitErrorMessage\\(error, 'upstream'\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/main/git/upstream.ts","lineNumber":71,"sourceCode":"      (args) => gitExecFileAsync(args, gitExecOptions(worktreePath, options)),\n      (upstreamName) => getBehindCommitsArePatchEquivalent(worktreePath, upstreamName, options)\n    )\n  } catch (error) {\n    // Why: we only swallow clearly-no-upstream signals — that's an expected\n    // state, not a failure. Other errors (auth, corruption, \"not a git\n    // repository\", sparse-checkout) should surface to the user so they can\n    // act on them. The shared isNoUpstreamError helper intentionally omits\n    // broad phrases like \"no such branch\" to avoid masking real errors.\n    if (isNoUpstreamError(error)) {\n      return {\n        hasUpstream: false,\n        ahead: 0,\n        behind: 0\n      }\n    }\n    // Why: parity with gitPush/gitPull/gitFetch — normalize before crossing\n    // the IPC boundary so renderers don't see execFile stderr preambles or local paths.\n    throw new Error(normalizeGitErrorMessage(error, 'upstream'))\n  }\n}\n","sourceCodeStart":53,"sourceCodeEnd":74,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/git/upstream.ts#L53-L74","documentation":"Thrown by getUpstreamStatus after the underlying git command (rev-list/log for ahead-behind counting) fails for any reason OTHER than a clearly-recognized 'no upstream configured' signal. The raw execFile error is passed through normalizeGitErrorMessage so that stderr preambles and local filesystem paths are stripped before the error crosses the IPC boundary into the renderer. It represents a genuine git failure (auth, corruption, 'not a git repository', sparse-checkout) that the user must act on, not an expected empty-upstream state.","triggerScenarios":"Calling getUpstreamStatus on a worktree whose .git metadata is corrupted; a sparse-checkout reconfigure that broke ref reads; a remote URL with expired/revoked credentials where the ahead-behind probe triggers a fetch; running against a directory that is no longer a git repository after a botched move; a git binary crash or lock contention (index.lock held).","commonSituations":"Worktree was moved or its administrative files deleted out-of-band; credentials helper returned an auth error during an implicit fetch; a concurrent git process holds index.lock; the repo lives on a network mount that dropped; sparse-checkout patterns reference paths git can't resolve.","solutions":["Read the normalized message text — it preserves the git failure category (auth/corruption/not-a-repo) which tells you the real cause.","From a terminal in the same worktree path, run the equivalent probe by hand (e.g. `git rev-list --left-right --count HEAD...@{u}`) to reproduce and see git's full stderr.","If auth-related, refresh credentials (`gh auth login` for GitHub, or the configured credential helper) and retry.","If 'not a git repository' or sparse-checkout related, verify the worktree path still exists and `git worktree list` is consistent; re-create the worktree if its admin files are gone.","Clear a stale `index.lock` only if no other git process is running."],"exampleFix":"// before: renderer trusts hasUpstream blindly and crashes on throw\nconst status = await getUpstreamStatus(path)\nif (!status.hasUpstream) publish()\n\n// after: surface the normalized git failure to the user\ntry {\n  const status = await getUpstreamStatus(path)\n  if (!status.hasUpstream) publish()\n} catch (err) {\n  toast.error(`Could not read upstream status: ${messageFromError(err)}`)\n}","handlingStrategy":"try-catch","validationCode":"// No pre-call validation can predict a git corruption / auth failure.\n// Validate the path is a non-empty string and a git repo before the call to\n// narrow the failure space:\nimport { existsSync } from 'node:fs'\nfunction assertWorktreePath(path: string) {\n  if (!path || typeof path !== 'string') throw new Error('worktree path required')\n  if (!existsSync(path)) throw new Error(`worktree path missing: ${path}`)\n}","typeGuard":"function isGitFailureWithMessage(err: unknown): err is Error {\n  return err instanceof Error && err.message.length > 0\n}","tryCatchPattern":"try {\n  const status = await getUpstreamStatus(worktreePath, pushTarget, options)\n  // use status\n} catch (err) {\n  // message is already normalized — show to user, do not log raw stderr\n  toast.error(`Upstream check failed: ${(err as Error).message}`)\n}","preventionTips":["Never swallow this throw silently — it carries a real git failure category.","Do not re-run the exact same probe in a tight loop; address the underlying git state first.","Keep credentials fresh so implicit fetches during the probe do not auth-fail."],"tags":["git","upstream","ipc","error-normalization"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}