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
- Ensure only one restore/agent mutates the workspace's head ref at a time (serialize restores per workspace).
- Remove stale ref locks if no git process is running: find <localDir>/.git -name '*.lock' and delete them after confirming no live git.
- Re-run the restore once the contending process has finished.
- 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
- Serialize restores per workspace so ref locks are uncontended.
- Clean up .git/*.lock files only after confirming no live git process owns them.
- Monitor for concurrent agent commits against the same branch ref.
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
- Failed to merge concurrent SSH git histories for ${currentHe
- Failed to compute a merged git tree for SSH workspace restor
- Timed out waiting for workspace restore lock at ${lockDir}
- Failed to integrate concurrent remote git history for ${inpu
- Invalid SSH environment variable key: ${key}
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/957b345ff901585f.
Report an issue: GitHub.