{"record":{"id":"599ba0695515d119","repo":"paperclipai/paperclip","slug":"filesystemscope-must-be-workspace","errorCode":null,"errorMessage":"filesystemScope must be \"workspace\".","messagePattern":"filesystemScope must be \"workspace\"\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/local-process-sandbox.ts","lineNumber":160,"sourceCode":"export function parseLocalProcessNetworkAllowlist(value: unknown): string[] {\n  if (!Array.isArray(value)) return [];\n  return value.map((entry, index) => {\n    if (typeof entry !== \"string\") throw new Error(`networkAllowlist[${index}] must be a string.`);\n    const rule = parseNetworkAllowlistEntry(entry, index);\n    return rule.port ? `${rule.hostname}:${rule.port}` : rule.hostname;\n  });\n}\n\nexport function parseLocalProcessNetworkScope(value: unknown): LocalProcessNetworkScope | null {\n  if (value == null || value === \"\") return null;\n  if (value === \"deny\" || value === \"allowlist\") return value;\n  throw new Error('networkScope must be \"deny\" or \"allowlist\".');\n}\n\nexport function parseLocalProcessFilesystemScope(value: unknown): \"workspace\" | null {\n  if (value == null || value === \"\") return null;\n  if (value === \"workspace\") return value;\n  throw new Error('filesystemScope must be \"workspace\".');\n}\n\nfunction isNetworkTargetAllowed(hostname: string, port: string, rules: NetworkAllowlistRule[]): boolean {\n  const normalizedHostname = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n  return rules.some((rule) => rule.hostname === normalizedHostname && (rule.port === null || rule.port === port));\n}\n\nfunction assertUnixSocketPathLength(socketPath: string): void {\n  const pathBytes = Buffer.byteLength(socketPath);\n  if (pathBytes > UNIX_SOCKET_PATH_MAX_BYTES) {\n    throw new Error(\n      `Paperclip sandbox proxy socket path is ${pathBytes} bytes, exceeding the Linux limit of ${UNIX_SOCKET_PATH_MAX_BYTES}: ${socketPath}`,\n    );\n  }\n}\n\nasync function createNetworkProxyTempDir(): Promise<string> {\n  const candidates = Array.from(new Set([\"/tmp\", os.tmpdir()]));","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/local-process-sandbox.ts#L142-L178","documentation":"Thrown by parseLocalProcessFilesystemScope when the supplied value is non-empty but not exactly \"workspace\". The filesystem sandbox currently implements exactly one mode: bind-mounting a workspace directory read-write inside a bubblewrap container and isolating everything else. Other scopes (e.g. read-only, host, custom) are not implemented, so any other literal is rejected rather than silently ignored.","triggerScenarios":"Calling parseLocalProcessFilesystemScope with values like \"read-only\", \"rw\", \"host\", \"full\", \"true\", true, or \"sandbox\". The function only accepts null/\"\" (-> null, scope unset) or the literal string \"workspace\"; everything else hits the throw at local-process-sandbox.ts:160.","commonSituations":"Config schemas that ship a generic enum without reading the adapter's accepted values, an older API that allowed \"rw\"/\"ro\", or a UI that submits the boolean true to enable filesystem isolation. Migration from another sandbox tool's vocabulary (firejail, nsjail) also produces mismatches.","solutions":["Pass \"workspace\" verbatim to enable filesystem sandboxing, or pass null/undefined/\"\" to leave it unset.","If your config layer exposes a richer enum, translate it to the adapter's vocabulary at the boundary: \"rw\" | \"ro\" -> \"workspace\".","If you genuinely need a non-workspace filesystem scope, that mode is not yet supported — file an issue and run without filesystemScope (rely on networkScope alone) until it lands.","Document the accepted literal next to the option in your config schema so callers do not guess."],"exampleFix":"// before\nconst fs = parseLocalProcessFilesystemScope(\"read-write\");\n\n// after\nconst fs = parseLocalProcessFilesystemScope(\"workspace\");","handlingStrategy":"validation","validationCode":"function normalizeFilesystemScope(value: unknown): \"workspace\" | null {\n  if (value == null || value === \"\") return null;\n  const legacy = { rw: \"workspace\", ro: \"workspace\", readwrite: \"workspace\" } as Record<string, string>;\n  const mapped = typeof value === \"string\" ? (legacy[value] ?? value) : value;\n  if (mapped !== \"workspace\") {\n    throw new Error(`Unsupported filesystemScope: ${String(value)} (only \"workspace\" is implemented)`);\n  }\n  return \"workspace\";\n}","typeGuard":"function isFilesystemScope(value: unknown): value is \"workspace\" {\n  return value === \"workspace\";\n}","tryCatchPattern":"try {\n  const scope = parseLocalProcessFilesystemScope(config.filesystemScope);\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('filesystemScope must be')) {\n    throw new ConfigError(`filesystemScope only supports \\\"workspace\\\" (got: ${String(config.filesystemScope)})`, { cause: error });\n  }\n  throw error;\n}","preventionTips":["Expose only the implemented scope in your config schema's enum.","Map legacy \"rw\"/\"ro\" values to \"workspace\" at the config boundary.","Document the accepted literal next to the option to prevent guessing."],"tags":["filesystem","config-validation","sandbox"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}