stablyai/orca · error
normalizeGitErrorMessage(error, 'push')
Error message
normalizeGitErrorMessage(error, 'push')
What it means
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.').
Source
Thrown at src/main/git/fork-sync.ts:32
options: GitRuntimeOptions = {}
): Promise<GitForkSyncResult> {
// Compose the caller's cancel signal with the 60s timeout so neither is lost —
// the caller's signal was previously clobbered by the timeout controller.
const signal = options.signal
? AbortSignal.any([options.signal, AbortSignal.timeout(60_000)])
: AbortSignal.timeout(60_000)
try {
return await syncForkDefaultBranch(
(args) =>
gitExecFileAsync(args, {
...gitOptionsForWorktree(worktreePath, options),
timeout: 60_000,
signal
}),
{ expectedUpstream }
)
} catch (error) {
throw new Error(normalizeGitErrorMessage(error, 'push'))
}
}
View on GitHub (pinned to 1136503c6a)
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.
Example fix
// before
await gitSyncForkDefaultBranch(worktreePath, expectedUpstream)
// after: classify the normalized message before retrying
try {
await gitSyncForkDefaultBranch(worktreePath, expectedUpstream)
} catch (error) {
const msg = error instanceof Error ? error.message : ''
if (msg.includes('non-fast-forward')) await resyncForkFromUpstream(worktreePath)
else if (msg.includes('Authentication failed')) await promptForCredentials(remote)
else throw error
} Defensive patterns
Strategy: try-catch
Validate before calling
import { getDefaultRemote } from './repo'
// Pre-check fork + upstream existence before sync.
const remote = await getDefaultRemote(worktreePath)
const { stdout } = await gitExecFileAsync(['remote', 'get-url', expectedUpstream.remoteName], gitExecOptions(worktreePath, {}))
if (!stdout.trim()) throw new Error('Upstream remote is not configured; cannot sync fork.') Type guard
// The thrown value is always a normalized Error; classify by the normalized message.
function isForkSyncNonFastForward(error: unknown): boolean {
return error instanceof Error && error.message.startsWith('Push rejected: remote has newer commits')
}
function isForkSyncAuth(error: unknown): boolean {
return error instanceof Error && error.message.startsWith('Authentication failed')
} Try / catch
try {
return await gitSyncForkDefaultBranch(worktreePath, expectedUpstream, options)
} catch (error) {
if (isForkSyncNonFastForward(error)) { await resetForkToUpstream(worktreePath); return await gitSyncForkDefaultBranch(worktreePath, expectedUpstream, options) }
if (isForkSyncAuth(error)) { await refreshUpstreamCredentials(worktreePath); return await gitSyncForkDefaultBranch(worktreePath, expectedUpstream, options) }
throw error
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- normalizeGitErrorMessage(error, 'push')
- normalizeGitErrorMessage(error, 'pull')
- normalizeGitErrorMessage(error, 'fetch')
- Repo has no configured git remotes.
- Repo has multiple remotes (${remotes.join(', ')}) and no def
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/34bfef48b1dbec3d.
Report an issue: GitHub.