{"record":{"id":"c9ced89d17777af8","repo":"paperclipai/paperclip","slug":"invalid-createos-file-mode","errorCode":null,"errorMessage":"Invalid CreateOS file mode.","messagePattern":"Invalid CreateOS file mode\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/file-sync.ts","lineNumber":96,"sourceCode":"        duplex: \"half\", headers: { \"Content-Type\": \"application/octet-stream\" }, signal,\n      };\n      const response = await client.request(`/sandboxes/${id}/files?path=${encodeURIComponent(remote)}`, init);\n      await response.body?.cancel();\n    } finally { source.destroy(); }\n  };\n  const download = async (remote: string, local: string, mode = 0o600) => {\n    const response = await client.request(`/sandboxes/${id}/files?path=${encodeURIComponent(remote)}`, { signal });\n    if (!response.body) throw new Error(\"CreateOS file download has no body.\");\n    await pipeline(response.body, createWriteStream(local, { flags: \"wx\", mode }), { signal });\n  };\n\n  // Validate every mapping before beginning side effects. Host paths are\n  // orchestrator-authored and checked by its source/target-root guard.\n  for (const operation of params.operations) {\n    for (const mapping of operation.files) {\n      if (![\"file\", \"directory\"].includes(mapping.kind)) throw new Error(\"Unsupported CreateOS transfer kind.\");\n      if (!path.isAbsolute(direction === \"in\" ? mapping.sourcePath : mapping.targetPath)) throw new Error(\"CreateOS transfer requires an absolute host path.\");\n      if (mapping.mode != null && (!Number.isInteger(mapping.mode) || mapping.mode < 0 || mapping.mode > 0o777)) throw new Error(\"Invalid CreateOS file mode.\");\n      assertRemotePath(direction === \"in\" ? mapping.targetPath : mapping.sourcePath);\n    }\n    for (const command of operation.postUploadCommands ?? []) {\n      assertRemotePath(command.cwd ?? ROOT);\n      if (command.timeoutMs != null && (!Number.isInteger(command.timeoutMs) || command.timeoutMs < 1 || command.timeoutMs > 86_400_000)) throw new Error(\"Invalid CreateOS transfer timeout.\");\n    }\n    if (direction === \"out\" && operation.postUploadCommands?.length) throw new Error(\"Outbound CreateOS transfers cannot run post-upload commands.\");\n  }\n\n  for (const operation of params.operations) {\n    let bytesTransferred = 0;\n    let filesTransferred = 0;\n    for (const mapping of operation.files) {\n      signal.throwIfAborted();\n      const local = direction === \"in\" ? mapping.sourcePath : mapping.targetPath;\n      const remote = direction === \"in\" ? mapping.targetPath : mapping.sourcePath;\n      const scratch = `/tmp/paperclip-createos-transfer-${randomUUID()}`;\n      // Outbound temporary files are on the target filesystem for atomic rename.","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/file-sync.ts#L78-L114","documentation":"Each mapping may carry an optional POSIX file `mode`. The plugin validates it is an integer within 0–0o777 (0–511 decimal) before executing the transfer. Non-integer values, negatives, or modes above 0777 (e.g. setuid/sticky bits included) are rejected with this error.","triggerScenarios":"A mapping sets mode to a non-integer (e.g. \"644\" string), a negative number, NaN, or a value > 0o777 such as 0o100644 (lstat-style mode) or 420 (decimal misinterpretation of 0644).","commonSituations":"Passing fs.Stats.mode (which includes file-type bits) directly instead of just permission bits; storing modes as decimal strings in config; JSON round-trips turning numbers into strings.","solutions":["Supply mode as a plain integer between 0 and 0o777, e.g. 0o600 or 420.","If you have a full lstat mode, mask it: mode & 0o777.","Ensure the value is a number type, not a string, before calling syncFiles."],"exampleFix":"// before\nconst mode = fs.statSync(local).mode; // e.g. 33188\n// after\nconst mode = fs.statSync(local).mode & 0o777; // e.g. 420 (0o644)","handlingStrategy":"validation","validationCode":"const isValidMode = (m: unknown) =>\n  Number.isInteger(m) && (m as number) >= 0 && (m as number) <= 0o777;\nif (mapping.mode != null && !isValidMode(mapping.mode)) throw new Error(\"bad mode\");","typeGuard":"const isPosixMode = (v: unknown): v is number =>\n  typeof v === \"number\" && Number.isInteger(v) && v >= 0 && v <= 0o777;","tryCatchPattern":null,"preventionTips":["Mask full stat modes with & 0o777 before use","Write modes as octal literals (0o600) to avoid decimal confusion","Keep modes as numbers through JSON serialization"],"tags":["validation","file-permissions","value-out-of-range"],"backgroundTag":"invalid-argument-value","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}