paperclipai/paperclip · error · Error

sandbox runtime asset key collides with a reserved runtime a

Error message

sandbox runtime asset key collides with a reserved runtime archive name: ${key}

What it means

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.

Source

Thrown at packages/adapter-utils/src/sandbox-managed-runtime.ts:436

// git-backed workspace, under `<runtimeRootDir>/git-workspace-upload.tar`. Each
// asset stages under `<runtimeRootDir>/<key>-upload.tar`, so an asset key equal to
// one of these stems resolves to the same remote archive path. Reserve the stems.
const RESERVED_RUNTIME_ASSET_KEYS = new Set(["workspace", "git-workspace"]);

// Reject an asset key before any path is built from it. An asset key becomes a
// remote directory (`<runtimeRootDir>/<key>`), a remote archive name
// (`<key>-upload.tar`), and a host temp file (`<key>.tar`). A path separator or
// `..` in the key escapes those roots. A reserved stem makes the asset archive
// share a path with the workspace archive; under concurrent sync the asset task
// and the workspace task then write or upload the same archive at the same time,
// which fails extraction nondeterministically or puts asset bytes in the
// workspace. Fail closed on both cases.
function assertRuntimeAssetKeyIsSafe(key: string): void {
  if (key.length === 0 || key.includes("/") || key.includes("\\") || key.includes("..")) {
    throw new Error(`sandbox runtime asset key is not a simple path segment: ${key}`);
  }
  if (RESERVED_RUNTIME_ASSET_KEYS.has(key)) {
    throw new Error(`sandbox runtime asset key collides with a reserved runtime archive name: ${key}`);
  }
}

export function parseSandboxRemoteExecutionSpec(value: unknown): SandboxRemoteExecutionSpec | null {
  const parsed = asObject(value);
  const transport = asString(parsed.transport).trim();
  const provider = asString(parsed.provider).trim();
  const sandboxId = asString(parsed.sandboxId).trim();
  const remoteCwd = asString(parsed.remoteCwd).trim();
  const timeoutMs = asNumber(parsed.timeoutMs);

  if (
    transport !== "sandbox" ||
    provider.length === 0 ||
    sandboxId.length === 0 ||
    remoteCwd.length === 0 ||
    !Number.isFinite(timeoutMs) ||
    timeoutMs <= 0

View on GitHub (pinned to a7e689b3c3)

Solutions

  1. Rename the key to any unreserved single-segment value, e.g. "workspace-assets" or "ws-overlay".
  2. Treat the reserved set {"workspace", "git-workspace"} as off-limits when generating keys programmatically (mirror RESERVED_RUNTIME_ASSET_KEYS).
  3. If you meant to sync the workspace itself, use the workspace sync path, not a runtime asset.

Example fix

// before
const assets = [{ key: "workspace", source: archivePath }];

// after
const assets = [{ key: "workspace-overlay", source: archivePath }];
Defensive patterns

Strategy: validation

Validate before calling

const RESERVED = new Set(["workspace", "git-workspace"]);
function isUsableRuntimeAssetKey(key: string): boolean {
  return isSafeRuntimeAssetKey(key) && !RESERVED.has(key);
}

Type guard

function isUnreservedAssetKey(key: string): boolean {
  return key !== "workspace" && key !== "git-workspace";
}

Try / catch

try {
  buildRuntimeAssets(assets);
} catch (error) {
  if (error instanceof Error && error.message.includes("reserved runtime archive name")) {
    assets = assets.map((a) => a.key === badKey ? { ...a, key: `${a.key}-assets` } : a);
  } else throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21). Data as JSON: /api/errors/af6ebd8f52e678f0. Report an issue: GitHub.