{"record":{"id":"584db90a459541f0","repo":"paperclipai/paperclip","slug":"paperclip-workspace-restore-preserved-local-work","errorCode":null,"errorMessage":"[paperclip] Workspace restore preserved local working tree changes after clean sandbox restore.","messagePattern":"\\[paperclip\\] Workspace restore preserved local working tree changes after clean sandbox restore\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"info","filePath":"packages/adapter-utils/src/git-workspace-sync.ts","lineNumber":702,"sourceCode":"  forceFullBundle?: boolean;\n}): string {\n  const remoteDir = shellQuote(input.remoteDir);\n  const bundlePath = shellQuote(input.bundlePath);\n  const exportRef = shellQuote(input.exportRef);\n  const baseSha = shellQuote(input.baseSha);\n  const statusPath = input.statusPath ? shellQuote(input.statusPath) : null;\n  const cleanupParts = [\n    `rm -f ${bundlePath}`,\n    ...(statusPath ? [`rm -f ${statusPath}`] : []),\n    `git -C ${remoteDir} update-ref -d ${exportRef} >/dev/null 2>&1 || true`,\n  ];\n  return [\n    \"set -e\",\n    input.cleanupBundle ? `cleanup() { ${cleanupParts.join(\"; \")}; }` : \"\",\n    input.cleanupBundle ? \"trap cleanup EXIT\" : \"\",\n    `mkdir -p ${shellQuote(path.posix.dirname(input.bundlePath))}`,\n    `rm -f ${bundlePath}`,\n    // Choose the bundle boundary. A thin bundle `HEAD --not <baseSha>` records\n    // baseSha as a prerequisite the importer (host) must already hold. That\n    // assumption breaks in two real cases, and then `git fetch` on the host\n    // hard-fails with \"did not send all necessary objects\" and the run's work\n    // is lost:\n    //   1. The sandbox HEAD has diverged from baseSha (e.g. a local-only branch\n    //      that forked from an older commit) — the host may still hold baseSha,\n    //      but a repo whose history is inconsistent cannot satisfy the walk.\n    //   2. The host workspace no longer holds baseSha at import time (a shared\n    //      workspace that was reset/re-realized between export and import).\n    // Bundle relative to the merge-base of baseSha and HEAD instead: that\n    // merge-base is an ancestor of baseSha, so any host that holds baseSha (or\n    // an ancestor of it) can satisfy the prerequisite, while the bundle stays a\n    // delta. When baseSha is absent from the sandbox — or no merge-base exists,\n    // or the caller forces it after a delta import failed on a missing\n    // prerequisite — fall back to a full, self-contained bundle with no\n    // prerequisites.\n    ...(input.forceFullBundle\n      ? [`bundle_base=\"\"`]","sourceCodeStart":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/adapter-utils/src/git-workspace-sync.ts#L684-L720","documentation":"resetLocalGitIndexToHead runs `git reset --quiet HEAD -- .` to unstage everything after a sandbox restore, asserts no staged changes remain (throws if staged diff is non-empty), then — only when checkWorkingTreeClean is set — runs `git diff --name-status HEAD`. Non-empty output means the restore left unstaged working-tree modifications, which the helper intentionally does NOT discard; this console.warn says those local changes were preserved through the clean-restore flow.","triggerScenarios":"A workspace that had local uncommitted edits (hotfixes, generated files) goes through a clean sandbox restore; the index resets to HEAD but the modified files remain on disk, so `git diff HEAD` still reports them.","commonSituations":"Agent run restored from a sandbox while a developer had local edits in the same checkout; generated artifacts differing from HEAD; end-of-line or permission-only diffs.","solutions":["Inspect what survived: run `git status --porcelain` and `git diff` in the workspace directory.","If you need a pristine tree, commit or stash the local changes, or explicitly clean them (git checkout -- . / git clean).","Treat the message as expected behavior when local edits existed before the restore — data was deliberately preserved."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { execSync } from \"node:child_process\";\nconst dirty = execSync(\"git status --porcelain\", { cwd: workspaceDir, encoding: \"utf8\" });\nif (dirty.trim().length > 0) {\n  // decide before restore: commit, stash, or accept that restore preserves these edits\n}","typeGuard":"const isCleanStatus = (porcelain: string): boolean => porcelain.trim().length === 0;","tryCatchPattern":null,"preventionTips":["Commit or stash local edits before triggering a clean sandbox restore if you want determinism.","Read the warning as informational: restore preserves local work by design.","After restore, run `git status --porcelain` to enumerate exactly what was kept."],"tags":["git","workspace-sync","sandbox-restore","working-tree"],"backgroundTag":"uncommitted-working-tree-changes","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}