{"record":{"id":"34bfef48b1dbec3d","repo":"stablyai/orca","slug":"normalizegiterrormessage-error-push","errorCode":null,"errorMessage":"normalizeGitErrorMessage(error, 'push')","messagePattern":"normalizeGitErrorMessage\\(error, 'push'\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/main/git/fork-sync.ts","lineNumber":32,"sourceCode":"  options: GitRuntimeOptions = {}\n): Promise<GitForkSyncResult> {\n  // Compose the caller's cancel signal with the 60s timeout so neither is lost —\n  // the caller's signal was previously clobbered by the timeout controller.\n  const signal = options.signal\n    ? AbortSignal.any([options.signal, AbortSignal.timeout(60_000)])\n    : AbortSignal.timeout(60_000)\n  try {\n    return await syncForkDefaultBranch(\n      (args) =>\n        gitExecFileAsync(args, {\n          ...gitOptionsForWorktree(worktreePath, options),\n          timeout: 60_000,\n          signal\n        }),\n      { expectedUpstream }\n    )\n  } catch (error) {\n    throw new Error(normalizeGitErrorMessage(error, 'push'))\n  }\n}\n","sourceCodeStart":14,"sourceCodeEnd":35,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/git/fork-sync.ts#L14-L35","documentation":"gitSyncForkDefaultBranch wraps syncForkDefaultBranch (the fork→upstream default-branch push used to keep a fork's default branch current). Any failure — git exec error, the composed 60s/AbortSignal timeout firing, a non-fast-forward rejection, or a non-Error thrown — is funneled through normalizeGitErrorMessage(error, 'push') and rethrown as a new Error. The literal message you see is the source expression; the runtime message is the normalized, credential-scrubbed tail of git's stderr (e.g. 'Push rejected: remote has newer commits (non-fast-forward). Please pull or sync first.' or 'Authentication failed. Check your remote credentials.').","triggerScenarios":"Calling gitSyncForkDefaultBranch(worktreePath, expectedUpstream, options) where the fork's default branch cannot be fast-forwarded to the upstream's default branch (divergence), the upstream URL requires credentials that are missing, the network is unreachable, the 60s timeout fires (slow upstream host), or options.signal aborts the composed signal.","commonSituations":"A fork that has commits its upstream does not (force-push needed, or the fork drifted); stored credentials expired or were never set for the upstream remote; the upstream host is slow or the connection drops mid-push; CI running on a slow network hitting the 60s budget; the fork-sync expectedUpstream identity is wrong (points at the wrong remote).","solutions":["Read the normalized message — it already tells you which class of failure occurred (non-fast-forward, auth, network, no upstream). Act on that specific hint first.","For non-fast-forward: inspect divergence between the fork default branch and the upstream default branch, then reset/rebase the fork before re-running fork-sync.","For auth failures: refresh credentials for the upstream remote (git remote get-url origin && git ls-remote to verify), then retry.","For timeouts on large upstreams: confirm the 60s budget is realistic for this repo size and network; fork-sync is intentionally bounded, so a bigger repo on a slow link may need a different sync strategy.","Pass options.signal only from a real user-cancel controller, not a short timeout — the 60s AbortSignal.timeout is already composed in for you."],"exampleFix":"// before\nawait gitSyncForkDefaultBranch(worktreePath, expectedUpstream)\n\n// after: classify the normalized message before retrying\ntry {\n  await gitSyncForkDefaultBranch(worktreePath, expectedUpstream)\n} catch (error) {\n  const msg = error instanceof Error ? error.message : ''\n  if (msg.includes('non-fast-forward')) await resyncForkFromUpstream(worktreePath)\n  else if (msg.includes('Authentication failed')) await promptForCredentials(remote)\n  else throw error\n}","handlingStrategy":"try-catch","validationCode":"import { getDefaultRemote } from './repo'\n\n// Pre-check fork + upstream existence before sync.\nconst remote = await getDefaultRemote(worktreePath)\nconst { stdout } = await gitExecFileAsync(['remote', 'get-url', expectedUpstream.remoteName], gitExecOptions(worktreePath, {}))\nif (!stdout.trim()) throw new Error('Upstream remote is not configured; cannot sync fork.')","typeGuard":"// The thrown value is always a normalized Error; classify by the normalized message.\nfunction isForkSyncNonFastForward(error: unknown): boolean {\n  return error instanceof Error && error.message.startsWith('Push rejected: remote has newer commits')\n}\nfunction isForkSyncAuth(error: unknown): boolean {\n  return error instanceof Error && error.message.startsWith('Authentication failed')\n}","tryCatchPattern":"try {\n  return await gitSyncForkDefaultBranch(worktreePath, expectedUpstream, options)\n} catch (error) {\n  if (isForkSyncNonFastForward(error)) { await resetForkToUpstream(worktreePath); return await gitSyncForkDefaultBranch(worktreePath, expectedUpstream, options) }\n  if (isForkSyncAuth(error)) { await refreshUpstreamCredentials(worktreePath); return await gitSyncForkDefaultBranch(worktreePath, expectedUpstream, options) }\n  throw error\n}","preventionTips":["Treat the normalized message as the source of truth — it already maps git stderr to an action class.","Do not pass options.signal from a short timeout; the 60s budget is composed in for you.","Verify the upstream remote resolves to the correct URL before invoking fork-sync."],"tags":["git","push","fork-sync","remote","normalized-error"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}