paperclipai/paperclip · error

Unsupported CreateOS transfer kind.

Error message

Unsupported CreateOS transfer kind.

What it means

syncFiles validates every file mapping's `kind` before performing any side effects. Only "file" and "directory" transfers are supported by the CreateOS provider; any other kind value fails fast with this error. It is a pre-flight guard so an unsupported transfer never partially executes.

Solutions

  1. Set mapping.kind to exactly "file" or "directory" for every mapping in the operation.
  2. Validate operation payloads against the PluginEnvironmentSyncResult/params schema before calling syncFiles.
  3. If you need symlink transfers, restructure them as file contents or add provider support — they are not supported today.

Example fix

// before
{ kind: "dir", sourcePath: "/host/app", targetPath: "/work/app" }
// after
{ kind: "directory", sourcePath: "/host/app", targetPath: "/work/app" }
Defensive patterns

Strategy: validation

Validate before calling

const ok = ops.every(op => op.files.every(f => f.kind === "file" || f.kind === "directory"));
if (!ok) throw new Error("mapping.kind must be 'file' or 'directory'");

Type guard

const isTransferKind = (k: unknown): k is "file" | "directory" =>
  k === "file" || k === "directory";

Prevention

When it happens

Trigger: An operation in params.operations contains a file mapping whose `kind` is not exactly "file" or "directory" (e.g. "symlink", a typo like "dir", or an undefined kind from a malformed operation object).

Common situations: Orchestrator or caller code constructs mappings by hand with wrong literal strings; a schema change upstream introduces a new transfer kind before the CreateOS adapter supports it; JSON payloads with case differences ("File").

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/d59fdfe7c692065a. Report an issue: GitHub.

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/file-sync.ts:94

      const init: RequestInit & { duplex: "half" } = {
        method: "PUT", body: Readable.toWeb(source) as ReadableStream<Uint8Array>,
        duplex: "half", headers: { "Content-Type": "application/octet-stream" }, signal,
      };
      const response = await client.request(`/sandboxes/${id}/files?path=${encodeURIComponent(remote)}`, init);
      await response.body?.cancel();
    } finally { source.destroy(); }
  };
  const download = async (remote: string, local: string, mode = 0o600) => {
    const response = await client.request(`/sandboxes/${id}/files?path=${encodeURIComponent(remote)}`, { signal });
    if (!response.body) throw new Error("CreateOS file download has no body.");
    await pipeline(response.body, createWriteStream(local, { flags: "wx", mode }), { signal });
  };

  // Validate every mapping before beginning side effects. Host paths are
  // orchestrator-authored and checked by its source/target-root guard.
  for (const operation of params.operations) {
    for (const mapping of operation.files) {
      if (!["file", "directory"].includes(mapping.kind)) throw new Error("Unsupported CreateOS transfer kind.");
      if (!path.isAbsolute(direction === "in" ? mapping.sourcePath : mapping.targetPath)) throw new Error("CreateOS transfer requires an absolute host path.");
      if (mapping.mode != null && (!Number.isInteger(mapping.mode) || mapping.mode < 0 || mapping.mode > 0o777)) throw new Error("Invalid CreateOS file mode.");
      assertRemotePath(direction === "in" ? mapping.targetPath : mapping.sourcePath);
    }
    for (const command of operation.postUploadCommands ?? []) {
      assertRemotePath(command.cwd ?? ROOT);
      if (command.timeoutMs != null && (!Number.isInteger(command.timeoutMs) || command.timeoutMs < 1 || command.timeoutMs > 86_400_000)) throw new Error("Invalid CreateOS transfer timeout.");
    }
    if (direction === "out" && operation.postUploadCommands?.length) throw new Error("Outbound CreateOS transfers cannot run post-upload commands.");
  }

  for (const operation of params.operations) {
    let bytesTransferred = 0;
    let filesTransferred = 0;
    for (const mapping of operation.files) {
      signal.throwIfAborted();
      const local = direction === "in" ? mapping.sourcePath : mapping.targetPath;
      const remote = direction === "in" ? mapping.targetPath : mapping.sourcePath;

View on GitHub (pinned to 3f1d897a7c)