{"record":{"id":"896da81c6570a2ba","repo":"agentscope-ai/agentscope","slug":"label-was-removed-or-replaced-before-execution","errorCode":null,"errorMessage":"{label} was removed or replaced before execution.","messagePattern":"(.+?) was removed or replaced before execution\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"critical","filePath":"src/agentscope/workspace/_bubblewrap/_bubblewrap_backend.py","lineNumber":475,"sourceCode":"\n    def _validate_mount_sources(self) -> None:\n        \"\"\"Ensure bind sources were not removed or replaced.\"\"\"\n        for label, path, expected_identity in (\n            (\n                \"host_workdir\",\n                self._host_workdir,\n                self._host_workdir_identity,\n            ),\n            (\n                \"host_tmpdir\",\n                self._host_tmpdir,\n                self._host_tmpdir_identity,\n            ),\n        ):\n            try:\n                identity = self._directory_identity(path, label=label)\n            except ValueError as exc:\n                raise RuntimeError(\n                    f\"{label} was removed or replaced before execution.\",\n                ) from exc\n            if identity != expected_identity:\n                raise RuntimeError(\n                    f\"{label} was replaced before execution.\",\n                )\n\n        if self._host_cache_dir is None:\n            return\n        try:\n            identity = self._directory_identity(\n                self._host_cache_dir,\n                label=\"host_cache_dir\",\n            )\n        except ValueError as exc:\n            raise RuntimeError(\n                \"host_cache_dir was removed or replaced before execution.\",\n            ) from exc","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/workspace/_bubblewrap/_bubblewrap_backend.py#L457-L493","documentation":"Before building the bwrap command line, the backend re-stats each configured host mount source and compares dev/ino identity. If _directory_identity raises (path is now a symlink or not a directory), this RuntimeError signals the directory was removed or swapped after the backend was constructed — a security guard against TOCTOU mount substitution.","triggerScenarios":"host_workdir or host_tmpdir (or an ancestor) being deleted and recreated, or replaced by a symlink, between backend construction and a sandbox execution.","commonSituations":"Cleanup jobs (tmpwatch, systemd-tmpfiles) pruning temp dirs mid-session; concurrent code recreating the workspace directory; an attacker-style symlink swap the guard is designed to catch.","solutions":["Recreate the BubblewrapBackend/Workspace with fresh directories after the old ones were removed","Move workspace dirs out of OS-managed temp cleaning policies (e.g. /var/lib/... rather than /tmp)","Avoid deleting/recreating mount source dirs while the workspace object lives"],"exampleFix":"# before\nshutil.rmtree(host_workdir); os.makedirs(host_workdir)  # mid-session\nawait ws.run_command('ls')  # RuntimeError\n# after\nawait ws.close()\nshutil.rmtree(host_workdir); os.makedirs(host_workdir)\nws = BubblewrapWorkspace(host_workdir=host_workdir)","handlingStrategy":"fallback","validationCode":"null","typeGuard":"null","tryCatchPattern":"try:\n    await ws.run_command(cmd)\nexcept RuntimeError as e:\n    if 'removed or replaced' in str(e):\n        ws = await rebuild_workspace()  # reconstruct backend","preventionTips":["Keep workspace dirs away from tmp-cleaners","Never rm -rf mount roots while a workspace object is alive"],"tags":["bubblewrap","toctou","security","mount"],"backgroundTag":"mount-source-disappeared","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}