stablyai/orca · error

normalizeGitErrorMessage(error, 'pull')

Error message

normalizeGitErrorMessage(error, 'pull')

What it means

gitPullWithArgs wraps its pull (used by gitPull and gitFastForward) so that after runPullWithDivergenceFallback exhausts its merge-retry fallback, any remaining failure is rethrown as new Error(normalizeGitErrorMessage(error, 'pull')). The literal message is the source expression; the runtime message is the normalized pull diagnostic — e.g. 'Pull needs a Git pull policy for divergent branches...' (only if the fallback could not apply), 'Pull would overwrite local changes...', 'Pull would overwrite untracked files...', 'Authentication failed.', or the tail stderr line.

Source

Thrown at src/main/git/remote.ts:248

      gitExecFileAsync(args, gitOptionsForWorktree(worktreePath, options))
    )
    if (upstream && !upstream.isConfiguredUpstream) {
      // Why: legacy Orca branches may still track origin/main while pushes
      // target origin/<branch>. Pull the same effective branch the UI reports.
      await gitExecFileAsync(
        ['pull', ...effectiveArgs, upstream.remoteName, upstream.branchName],
        gitOptionsForWorktree(worktreePath, options)
      )
      return
    }

    await gitExecFileAsync(['pull', ...effectiveArgs], gitOptionsForWorktree(worktreePath, options))
  }

  try {
    await runPullWithDivergenceFallback(pullArgs, runPull)
  } catch (error) {
    throw new Error(normalizeGitErrorMessage(error, 'pull'))
  }
}

export async function gitPull(
  worktreePath: string,
  pushTarget?: GitPushTarget,
  options: GitRuntimeOptions = {}
): Promise<void> {
  // Why: plain `git pull` uses the user's configured pull strategy (merge by
  // default) so diverged branches reconcile instead of erroring out. Conflicts
  // surface through the existing conflict-resolution flow.
  await runWithGitReadCacheInvalidation(() =>
    gitPullWithArgs(worktreePath, [], pushTarget, options)
  )
}

export async function gitFastForward(
  worktreePath: string,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Read the normalized message — it pins the failure class (overwrites untracked/working-tree, divergence policy, auth, network).
  2. For 'would overwrite local changes': commit, stash, or discard the listed files before pulling.
  3. For 'would overwrite untracked files': move, remove, or git-add the colliding untracked paths first.
  4. For divergence-policy on a fast-forward pull: the --ff-only path intentionally skips the merge fallback — use gitPull (plain) instead of gitFastForward when divergence is expected.
  5. For auth: refresh credentials for the resolved upstream remote.

Example fix

// before
await gitFastForward(worktreePath) // --ff-only; no merge fallback

// after: use plain pull when divergence is possible so the merge fallback can apply
await gitPull(worktreePath)
Defensive patterns

Strategy: try-catch

Validate before calling

import { isDirtyWorktree, listUntrackedPaths } from './status'

// Pre-check: pull cannot overwrite a dirty worktree.
if (await isDirtyWorktree(worktreePath)) {
  throw new Error('Commit or stash local changes before pulling.')
}

Type guard

function isPullOverwriteLocal(error: unknown): boolean {
  return error instanceof Error && error.message.startsWith('Pull would overwrite local changes')
}
function isPullOverwriteUntracked(error: unknown): boolean {
  return error instanceof Error && error.message.startsWith('Pull would overwrite untracked files')
}

Try / catch

try {
  await gitPull(worktreePath, pushTarget, options)
} catch (error) {
  if (isPullOverwriteLocal(error)) { await stashAll(worktreePath); await gitPull(worktreePath, pushTarget, options); await popStash(worktreePath) }
  else if (isPullOverwriteUntracked(error)) { await moveUntrackedAside(worktreePath); await gitPull(worktreePath, pushTarget, options) }
  else throw error
}

Prevention

When it happens

Trigger: Calling gitPull or gitFastForward when local working-tree changes would be overwritten by the incoming merge, untracked files collide with fetched paths, credentials are missing, the divergence-fallback itself fails (e.g. pullArgs already specified --ff-only so the --no-rebase fallback was skipped), or the upstream is unreachable.

Common situations: Pulling into a dirty worktree with conflicting tracked files; pulling a branch that adds files already present as untracked; a repo with no pull.rebase/pull.ff policy where the --ff-only fast-forward path (gitFastForward) cannot apply the merge fallback because pullArgsSpecifyReconciliation returned true; expired remote credentials; reviewer pulls a stale review branch whose upstream was force-pushed.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/216f26dcc0057f83. Report an issue: GitHub.