paperclipai/paperclip · error · Error

Failed to integrate concurrent SSH git history for ${input.i

Error message

Failed to integrate concurrent SSH git history for ${input.importedHead.slice(0, 12)} after multiple retries.

What it means

Thrown by mergeConcurrentSshGitHistories after the loop ran all 5 attempts without returning. Each attempt ends by calling `git update-ref` to move the head ref to the merged commit, and a 'cannot lock ref ... expected' error triggers a retry; after attempt 4 with still-concurrent ref-update failures the loop exits and this error fires. It indicates persistent ref-lock contention, not a content conflict.

Source

Thrown at packages/adapter-utils/src/ssh.ts:1003

      ],
      {
        timeout: 60_000,
        maxBuffer: 64 * 1024,
      },
    );
    try {
      await runLocalGit(input.localDir, ["update-ref", headRef, mergeCommit.stdout.trim(), currentHead], {
        timeout: 10_000,
        maxBuffer: 16 * 1024,
      });
      return;
    } catch (error) {
      if (isConcurrentRefUpdateError(error) && attempt < 4) continue;
      throw error;
    }
  }

  throw new Error(`Failed to integrate concurrent SSH git history for ${input.importedHead.slice(0, 12)} after multiple retries.`);
}

async function clearRemoteDirectory(input: {
  spec: SshConnectionConfig;
  remoteDir: string;
  preserveEntries?: string[];
}): Promise<void> {
  const preservePatterns = (input.preserveEntries ?? [])
    .map((entry) => `! -name ${shellQuote(entry)}`)
    .join(" ");
  const script = [
    "set -e",
    `mkdir -p ${shellQuote(input.remoteDir)}`,
    `find ${shellQuote(input.remoteDir)} -mindepth 1 -maxdepth 1 ${preservePatterns} -exec rm -rf -- {} +`,
  ].join("\n");
  await runSshScript(input.spec, script, {
    timeoutMs: 30_000,
    maxBuffer: 256 * 1024,

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Ensure only one restore/agent mutates the workspace's head ref at a time (serialize restores per workspace).
  2. Remove stale ref locks if no git process is running: find <localDir>/.git -name '*.lock' and delete them after confirming no live git.
  3. Re-run the restore once the contending process has finished.
  4. If contention is structural, reduce parallelism for that workspace's lane.
Defensive patterns

Strategy: retry

Try / catch

try {
  await mergeConcurrentSshGitHistories(input);
} catch (err) {
  if (err.message.includes("after multiple retries")) {
    // clear stale ref locks if no git process is live, then retry once
  }
  throw err;
}

Prevention

When it happens

Trigger: Five consecutive update-ref calls fail with isConcurrentRefUpdateError (message contains 'cannot lock ref' and 'expected'). This happens when another process holds/contends the git ref lock during the entire retry window — e.g. another restore, a concurrent agent commit, or a stale .git/refs lock file.

Common situations: Multiple restores or agent runs targeting the same local workspace dir simultaneously; a stale .git/<ref>.lock left by a killed process; an aggressively concurrent pipeline that races the same branch ref.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/957b345ff901585f. Report an issue: GitHub.