{"record":{"id":"de73daadca601cb2","repo":"abhigyanpatwari/GitNexus","slug":"dependency-symlink-changed-while-snapshotting-re","errorCode":null,"errorMessage":"dependency symlink changed while snapshotting: {relative}","messagePattern":"dependency symlink changed while snapshotting: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/task_assets.py","lineNumber":490,"sourceCode":"            raise SandboxError(f\"dependency symlink is unreadable or not UTF-8: {relative}\") from exc\n        if not target or PurePosixPath(target).is_absolute() or \"\\x00\" in target:\n            raise SandboxError(f\"dependency symlink must be a bounded relative link: {relative}\")\n        if len(target_bytes) > MAX_TASK_ASSET_PATH_BYTES:\n            raise SandboxError(f\"dependency symlink target exceeds the path limit: {relative}\")\n        if self.budget.total_bytes + len(target_bytes) > MAX_TASK_ASSET_BYTES:\n            raise SandboxError(\"sandbox_copy exceeds the total byte limit\")\n        destination = self.destination / Path(*relative.parts)\n        os.symlink(target, destination)\n        after = os.stat(name, dir_fd=parent_descriptor, follow_symlinks=False)\n        if (\n            _mutation_identity(before) != _mutation_identity(after)\n            or os.readlink(\n                name,\n                dir_fd=parent_descriptor,\n            )\n            != target\n        ):\n            raise SandboxError(f\"dependency symlink changed while snapshotting: {relative}\")\n        self.total_bytes += len(target_bytes)\n        self.budget.total_bytes += len(target_bytes)\n        self._record(\n            AssetManifestEntry(\n                path=relative,\n                kind=\"symlink\",\n                size=len(target_bytes),\n                sha256=hashlib.sha256(target_bytes).hexdigest(),\n                link_target=target,\n            )\n        )\n\n    def _ensure_parents(self, relative: PurePosixPath) -> None:\n        current = PurePosixPath()\n        for part in relative.parts:\n            current /= part\n            existing = self.entries.get(current)\n            if existing is not None:","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/task_assets.py#L472-L508","documentation":"Raised by _copy_symlink after creating the destination link: if a re-stat of the source link shows a different mutation identity (dev, ino, mode, size, mtime_ns, ctime_ns) or a second readlink returns a different target string, the snapshot is rejected as inconsistent. This is the symlink analogue of the file-changed TOCTOU guard and guarantees the captured link target matches what was on disk at the recorded moment.","triggerScenarios":"The symlink is deleted and recreated (changing ino), retargeted (`ln -sfn` by another process), or its metadata changed (chmod) between the initial stat (passed as `before`) and the post-copy verification. Most common when a package manager or build tool rewrites node_modules links concurrently with snapshot capture.","commonSituations":"npm/pnpm/yarn reinstalling or deduplicating links while the benchmark captures a dependency snapshot. A watch mode tool (vite, nodemon) touching node_modules. Running snapshot capture against a working tree another process is mutating.","solutions":["Quiesce the tree before capture: stop package managers, watch modes, and any process that may touch node_modules.","Capture against a clean checkout — `git worktree add` at the resolved SHA and install dependencies into that isolated tree before snapshotting.","Re-run after the tree is stable; this is a transient race.","If reproducible, identify which process rewrites the link and sequence it out of the capture window."],"exampleFix":"# before — capturing while pnpm deduplicates\npnpm install &\nsnapshot = cache.prepare(task, repo=repo, resolved_sha=sha)\n\n# after — finish install, then capture\npnpm install\nwait\nsnapshot = cache.prepare(task, repo=repo, resolved_sha=sha)","handlingStrategy":"validation","validationCode":"from pathlib import Path\nimport os, time\n\ndef assert_dep_symlinks_stable(dep_source: Path, gap: float = 0.5) -> None:\n    def fp():\n        out = {}\n        for current, dirs, files in os.walk(dep_source, followlinks=False):\n            for name in dirs + files:\n                p = Path(current) / name\n                if p.is_symlink():\n                    st = p.lstat()\n                    out[str(p)] = (st.st_ino, st.st_mtime_ns, os.readlink(p))\n        return out\n    a = fp(); time.sleep(gap); b = fp()\n    if a != b:\n        raise ValueError(f\"dependency symlinks not stable: {[k for k in a if a[k]!=b.get(k)][:5]}\")\n\nfor d in task.get(\"sandbox_dependencies\", []):\n    assert_dep_symlinks_stable(repo_path / d[\"source\"])","typeGuard":null,"tryCatchPattern":"from eval.workflow_bench.propposer_sandbox import SandboxError\n\ntry:\n    snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)\nexcept SandboxError as exc:\n    if \"changed while snapshotting\" in str(exc):\n        # stop package managers / watch modes, then retry once\n        raise\n    raise","preventionTips":["Finish all package-manager operations (npm/pnpm/yarn install) before capture.","Stop watch modes that touch node_modules (vite, nodemon, webpack-dev-server).","Capture against an isolated git worktree so no concurrent process mutates links."],"tags":["sandbox","symlink","dependencies","concurrency","toctou","integrity"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}