{"record":{"id":"af6ebd8f52e678f0","repo":"paperclipai/paperclip","slug":"sandbox-runtime-asset-key-collides-with-a-reserved","errorCode":null,"errorMessage":"sandbox runtime asset key collides with a reserved runtime archive name: ${key}","messagePattern":"sandbox runtime asset key collides with a reserved runtime archive name: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/sandbox-managed-runtime.ts","lineNumber":445,"sourceCode":"}\n\n/**\n * A control command run against the sandbox after a sync operation's files have\n * landed. Mirrors the plugin SDK `PluginPostUploadCommand`; kept as a local\n * structural type so `adapter-utils` does not depend on the plugin SDK. Ordered\n * within {@link SandboxSyncOperation.postUploadCommands} and executed in array\n * order, fail-fast (first non-zero exit or timeout aborts the operation).\n *\n * SECURITY — command origin (Stage-1 design review, condition C1). `command` is\n * a **Paperclip/adapter-authored control operation**: it may be supplied ONLY by\n * core/adapter code. No server route, issue/comment content, project/workspace\n * file content, provider-plugin callback, or arbitrary adapter config may supply\n * a raw `command` string; any path embedded in it MUST be built by adapter/core\n * helpers from already-confined paths and shell-quoted (C3). Providers treat the\n * command as **opaque** — execute or reject, never rewrite/concatenate/append.\n */\nexport interface SandboxPostUploadCommand {\n  /** The opaque, adapter-authored shell command to run after upload. */\n  command: string;\n  /**\n   * Working directory for the command. When present, MUST be an absolute POSIX\n   * path confined under the operation's allowed sandbox target root (C2). When\n   * absent, defaults to the runtime's stable command cwd — never a process\n   * default cwd.\n   */\n  cwd?: string;\n  /** Optional per-command timeout in milliseconds. */\n  timeoutMs?: number;\n}\n\n/**\n * An ordered, opaque unit of work handed to the native sync transport. The\n * `operationId` is an opaque, non-sensitive token authored by the orchestrator\n * (never a caller/asset identifier that could leak intent); a provider MUST NOT\n * interpret it.\n */","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/adapter-utils/src/sandbox-managed-runtime.ts#L427-L463","documentation":"Thrown by assertRuntimeAssetKeyIsSafe when a runtime asset key is exactly \"workspace\" or \"git-workspace\". Those stems are reserved: the asset archive would share its path with the workspace archive, and under concurrent sync the two tasks would write/upload the same file simultaneously, corrupting extraction or leaking asset bytes into the workspace, so the runtime fails closed.","triggerScenarios":"A SandboxRemoteExecutionSpec runtimeAssets entry uses key \"workspace\" or \"git-workspace\" (e.g. author intended to ship a prebuilt workspace); assertRuntimeAssetKeyIsSafe rejects it before sync at sandbox-managed-runtime.ts:774.","commonSituations":"Reusing a natural directory name as the asset key; converting an older manifest that happened to name its asset \"workspace\"; tooling that defaults keys to the top-level folder name being archived.","solutions":["Rename the key to any unreserved single-segment value, e.g. \"workspace-assets\" or \"ws-overlay\".","Treat the reserved set {\"workspace\", \"git-workspace\"} as off-limits when generating keys programmatically (mirror RESERVED_RUNTIME_ASSET_KEYS).","If you meant to sync the workspace itself, use the workspace sync path, not a runtime asset."],"exampleFix":"// before\nconst assets = [{ key: \"workspace\", source: archivePath }];\n\n// after\nconst assets = [{ key: \"workspace-overlay\", source: archivePath }];","handlingStrategy":"validation","validationCode":"const RESERVED = new Set([\"workspace\", \"git-workspace\"]);\nfunction isUsableRuntimeAssetKey(key: string): boolean {\n  return isSafeRuntimeAssetKey(key) && !RESERVED.has(key);\n}","typeGuard":"function isUnreservedAssetKey(key: string): boolean {\n  return key !== \"workspace\" && key !== \"git-workspace\";\n}","tryCatchPattern":"try {\n  buildRuntimeAssets(assets);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"reserved runtime archive name\")) {\n    assets = assets.map((a) => a.key === badKey ? { ...a, key: `${a.key}-assets` } : a);\n  } else throw error;\n}","preventionTips":["Treat {\"workspace\", \"git-workspace\"} as a blocklist when generating keys.","Do not name assets after top-level runtime directories.","Add a manifest lint step that flags reserved keys before submission."],"tags":["sandbox","assets","validation","reserved-name","concurrency"],"backgroundTag":"reserved-name-collision","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-08-21T17:58:32.592Z","contentChangedAt":"2026-08-21T17:58:32.592Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}