{"record":{"id":"957b345ff901585f","repo":"paperclipai/paperclip","slug":"failed-to-integrate-concurrent-ssh-git-history-for","errorCode":null,"errorMessage":"Failed to integrate concurrent SSH git history for ${input.importedHead.slice(0, 12)} after multiple retries.","messagePattern":"Failed to integrate concurrent SSH git history for (.+?) after multiple retries\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/ssh.ts","lineNumber":1003,"sourceCode":"      ],\n      {\n        timeout: 60_000,\n        maxBuffer: 64 * 1024,\n      },\n    );\n    try {\n      await runLocalGit(input.localDir, [\"update-ref\", headRef, mergeCommit.stdout.trim(), currentHead], {\n        timeout: 10_000,\n        maxBuffer: 16 * 1024,\n      });\n      return;\n    } catch (error) {\n      if (isConcurrentRefUpdateError(error) && attempt < 4) continue;\n      throw error;\n    }\n  }\n\n  throw new Error(`Failed to integrate concurrent SSH git history for ${input.importedHead.slice(0, 12)} after multiple retries.`);\n}\n\nasync function clearRemoteDirectory(input: {\n  spec: SshConnectionConfig;\n  remoteDir: string;\n  preserveEntries?: string[];\n}): Promise<void> {\n  const preservePatterns = (input.preserveEntries ?? [])\n    .map((entry) => `! -name ${shellQuote(entry)}`)\n    .join(\" \");\n  const script = [\n    \"set -e\",\n    `mkdir -p ${shellQuote(input.remoteDir)}`,\n    `find ${shellQuote(input.remoteDir)} -mindepth 1 -maxdepth 1 ${preservePatterns} -exec rm -rf -- {} +`,\n  ].join(\"\\n\");\n  await runSshScript(input.spec, script, {\n    timeoutMs: 30_000,\n    maxBuffer: 256 * 1024,","sourceCodeStart":985,"sourceCodeEnd":1021,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/ssh.ts#L985-L1021","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await mergeConcurrentSshGitHistories(input);\n} catch (err) {\n  if (err.message.includes(\"after multiple retries\")) {\n    // clear stale ref locks if no git process is live, then retry once\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["git","ssh","workspace-restore","concurrency","retry-exhausted","paperclip"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}