{"record":{"id":"948e14d007d92ff9","repo":"paperclipai/paperclip","slug":"createos-transfer-requires-an-absolute-host-path","errorCode":null,"errorMessage":"CreateOS transfer requires an absolute host path.","messagePattern":"CreateOS transfer requires an absolute host path\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/file-sync.ts","lineNumber":95,"sourceCode":"        method: \"PUT\", body: Readable.toWeb(source) as ReadableStream<Uint8Array>,\n        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()}`;","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/file-sync.ts#L77-L113","documentation":"For inbound transfers the mapping's sourcePath is on the local host, and for outbound transfers the targetPath is; the CreateOS plugin requires that host-side path to be absolute. The check runs during validation before any files move, throwing this error when a relative host path is supplied.","triggerScenarios":"syncFiles is called with a mapping where `path.isAbsolute(sourcePath)` fails for direction \"in\" or `path.isAbsolute(targetPath)` fails for direction \"out\" — e.g. paths built from relative cwd strings like \"./data\" or \"data/file.txt\".","commonSituations":"Caller constructs paths from user-relative working directories without path.resolve; config values hold relative paths; code ported from another provider that accepted relative paths.","solutions":["Resolve host paths to absolute before building the operation, e.g. path.resolve(cwd, relative).","For direction \"in\", make sourcePath absolute; for direction \"out\", make targetPath absolute.","Add an assertion in the calling code that fails early with the offending path for easier diagnosis."],"exampleFix":"// before\nsyncFiles({ direction: \"in\", operations: [{ files: [{ kind: \"file\", sourcePath: \"./out.json\", targetPath: \"/work/out.json\" }] }] });\n// after\nimport path from \"node:path\";\nsyncFiles({ direction: \"in\", operations: [{ files: [{ kind: \"file\", sourcePath: path.resolve(\"./out.json\"), targetPath: \"/work/out.json\" }] }] });","handlingStrategy":"validation","validationCode":"import path from \"node:path\";\nconst hostPath = direction === \"in\" ? mapping.sourcePath : mapping.targetPath;\nif (!path.isAbsolute(hostPath)) throw new Error(`host path must be absolute: ${hostPath}`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass host paths through path.resolve()","Never build host paths from unanchored relative config values","Assert absoluteness at the boundary where operations are constructed"],"tags":["validation","path","file-transfer"],"backgroundTag":"invalid-argument-format","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"}