paperclipai/paperclip · error

Daytona post-upload command failed

Error message

Daytona post-upload command failed (exit ${result.exitCode ?? "unknown"})${detail ? `: ${detail}` : ""}

What it means

Post-upload failure check in syncInFileMappings: a caller-supplied postUploadCommand run verbatim in the sandbox (after the cwd symlink-escape guard) exited non-zero or timed out, so the remaining queued commands are aborted per the C4 fail-fast rule. The detail includes the command's captured output.

Solutions

  1. Inspect the exit code and detail; fix the post-upload command and retry.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at packages/plugins/sandbox-providers/daytona/src/file-sync.ts:767 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at packages/plugins/sandbox-providers/daytona/src/file-sync.ts:789

      if (plan.compressed) {
        // `zstd -d -o` copies the mode of its INPUT (the uploaded `.zst`
        // scratch) onto its output with its own `chmod` call. That call runs
        // AFTER creation, so it overrides any `umask` in effect — a mapping
        // with no explicit `mode` must not rely on the scratch file's mode
        // being owner-only already. Always `chmod` the decompressed file
        // right after decompression: to the mapping's `mode` when set, or to
        // owner-only (0600) otherwise. The pre-refactor decompression step
        // applied the same 0600 default. The raw (uncompressed) path above
        // applies no `chmod` when the mapping sets no `mode`, so the two
        // inbound branches do not use the same no-mode default today.
        const targetMode = typeof plan.mapping.mode === "number" ? plan.mapping.mode : 0o600;
        renameScript.push(
          `zstd -d -o ${shellQuote(plan.rawScratch)} ${shellQuote(plan.compressed.zstdScratch)} || { echo "decompress failed"; exit 49; };`,
          `chmod ${toOctalModeString(targetMode)} ${shellQuote(plan.rawScratch)} || { echo "chmod failed"; exit 50; };`,
        );
      }
      renameScript.push(
        `mv -f ${shellQuote(plan.rawScratch)} ${shellQuote(plan.mapping.targetPath)} || { echo "rename failed"; exit 43; };`,
      );
      if (plan.compressed) {
        // Clean up the `.zst` scratch after a successful promotion. `|| true`
        // keeps a cleanup failure from becoming the promote script's own exit
        // status — every target file is already in place by this point, so a
        // stray `.zst` scratch must never read back as a sync failure.
        renameScript.push(`rm -f ${shellQuote(plan.compressed.zstdScratch)} || true;`);
      }
    }
    // `promote` span: move the staged temp onto its target. When this batch
    // decompressed at least one mapping, this span also carries
    // `transfer.decompress.wall_ms`. That value measures the WHOLE promote
    // command — every decompression and every `mv` — not decompression alone.
    // Treat it as an upper bound on the decompress wall time, not an exact
    // measurement.
    await withProviderSpan({
      name: "promote",
      wallMsAttr: hasCompressedMapping ? SPAN_ATTR.transferDecompressWallMs : undefined,

View on GitHub (pinned to 3f1d897a7c)