{"record":{"id":"cda5238318ba18b8","repo":"paperclipai/paperclip","slug":"invalid-createos-transfer-timeout","errorCode":null,"errorMessage":"Invalid CreateOS transfer timeout.","messagePattern":"Invalid CreateOS transfer timeout\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/file-sync.ts","lineNumber":101,"sourceCode":"  };\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.\n      const parent = direction === \"out\" ? path.dirname(local) : os.tmpdir();\n      await fs.mkdir(parent, { recursive: true });\n      const temp = await fs.mkdtemp(path.join(parent, \".paperclip-createos-\"));\n      const transferFile = path.join(temp, \"data\");\n      try {","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/file-sync.ts#L83-L119","documentation":"postUploadCommands may specify an optional timeoutMs for a remote command. The plugin validates it is an integer between 1 and 86,400,000 ms (24 hours). Missing, non-integer, zero, negative, or excessively large values throw this error during pre-flight validation.","triggerScenarios":"A post-upload command defines timeoutMs as 0, a float, a negative number, a string like \"30000\", or a value exceeding one day (e.g. Number.MAX_SAFE_INTEGER intending \"no timeout\").","commonSituations":"Using Infinity/very large sentinels to mean unlimited; config parsing yields strings; unit confusion (seconds written where milliseconds are expected, e.g. 30 instead of 30_000).","solutions":["Set timeoutMs to a positive integer of milliseconds within 1..86_400_000, e.g. 30_000.","Omit timeoutMs entirely to use the plugin's default signal handling instead of a sentinel value.","Coerce and validate numeric config values (Number(), Number.isInteger) before constructing operations."],"exampleFix":"// before\n{ cmd: \"./postprocess.sh\", timeoutMs: \"30s\" }\n// after\n{ cmd: \"./postprocess.sh\", timeoutMs: 30_000 }","handlingStrategy":"validation","validationCode":"const isValidTimeout = (t: unknown) =>\n  Number.isInteger(t) && (t as number) >= 1 && (t as number) <= 86_400_000;\nfor (const c of op.postUploadCommands ?? []) {\n  if (c.timeoutMs != null && !isValidTimeout(c.timeoutMs)) throw new Error(\"bad timeoutMs\");\n}","typeGuard":"const isTimeoutMs = (v: unknown): v is number =>\n  typeof v === \"number\" && Number.isInteger(v) && v >= 1 && v <= 86_400_000;","tryCatchPattern":null,"preventionTips":["Use millisecond integers, never strings or second values","Omit timeoutMs rather than passing Infinity or huge sentinels","Coerce config-driven numbers with Number() and Number.isInteger checks"],"tags":["validation","timeout","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"}