paperclipai/paperclip · error · Error

${action} failed with exit code ${result.exitCode ?? "null"}

Error message

${action} failed with exit code ${result.exitCode ?? "null"}${detail}

What it means

Thrown by requireSuccessfulResult when a sandbox command executed via the command-managed runtime returns a non-zero exit code or times out. This is the generic failure wrapper for all shell commands run through runShell (makeDir, writeFile, readFile, listFiles, remove, run) and post-upload commands. The error message includes the action (the shell script or command label), the exit code (or 'null' for timeout), and a truncated tail of stderr and stdout.

Source

Thrown at packages/adapter-utils/src/command-managed-runtime.ts:130

function formatFailedCommandOutput(result: RunProcessResult): string {
  const tail = (text: string): string => {
    const trimmed = text.trim();
    if (trimmed.length <= FAILED_COMMAND_OUTPUT_TAIL_CHARS) return trimmed;
    return `...[truncated]\n${trimmed.slice(-FAILED_COMMAND_OUTPUT_TAIL_CHARS)}`;
  };
  const stderr = tail(result.stderr);
  const stdout = tail(result.stdout);
  const parts: string[] = [];
  if (stderr.length > 0) parts.push(`stderr: ${stderr}`);
  if (stdout.length > 0) parts.push(`stdout: ${stdout}`);
  return parts.length > 0 ? `:\n${parts.join("\n")}` : "";
}

function requireSuccessfulResult(result: RunProcessResult, action: string): void {
  if (result.exitCode === 0 && !result.timedOut) return;
  const detail = formatFailedCommandOutput(result);
  throw new Error(`${action} failed with exit code ${result.exitCode ?? "null"}${detail}`);
}

function bufferToArrayBuffer(buffer: Buffer): ArrayBuffer {
  // Copy out of the (possibly pooled) Node Buffer so the ArrayBuffer we hand to
  // the client transport owns exactly these bytes.
  return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;
}

// Named builder (Security Condition C3): extract an uploaded tarball into its
// target directory as a clean destroy-then-replace, then remove the tarball.
// Every path is shell-quoted; the fallback NEVER concatenates untrusted asset
// keys / file names into the shell.
function buildSyncInExtractDirectoryCommand(input: { remoteTarPath: string; targetDir: string }): string {
  return (
    `rm -rf ${shellQuote(input.targetDir)} && ` +
    `mkdir -p ${shellQuote(input.targetDir)} && ` +
    `tar -xf ${shellQuote(input.remoteTarPath)} -C ${shellQuote(input.targetDir)} && ` +
    `rm -f ${shellQuote(input.remoteTarPath)}`

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Read the stderr/stdout tail in the error message to identify the specific shell error.
  2. If the command timed out, increase the timeoutMs in the CommandManagedRuntimeSpec or per-command timeout.
  3. Verify the sandbox has the required tools: check that bash/sh, base64, dd, tar, wc are available.
  4. For post-upload command failures, run the failing command manually in the sandbox to reproduce and debug.
  5. Check sandbox disk space and permissions if the error is I/O related.

Example fix

// before: post-upload command fails (e.g., npm install in wrong cwd)
const ops: SandboxSyncOperation[] = [{
  files: [{ kind: "directory", sourcePath: "./proj", targetPath: "/workspace/proj" }],
  postUploadCommands: [{ command: "npm install", cwd: "/workspace/wrong-path" }],
}];

// after: correct cwd matching targetPath
const ops: SandboxSyncOperation[] = [{
  files: [{ kind: "directory", sourcePath: "./proj", targetPath: "/workspace/proj" }],
  postUploadCommands: [{ command: "npm install", cwd: "/workspace/proj" }],
}];
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate sandbox environment before running commands:
async function verifySandboxReady(client: SandboxManagedRuntimeClient): Promise<void> {
  // Check essential tools exist
  const tools = ['bash', 'base64', 'dd', 'tar', 'wc'];
  for (const tool of tools) {
    try {
      await client.run(`command -v ${tool}`, { timeoutMs: 5000 });
    } catch {
      throw new Error(`Required tool '${tool}' is missing from the sandbox environment.`);
    }
  }
}

// Call before syncIn or other operations:
await verifySandboxReady(client);

Try / catch

try {
  await client.run(command, { timeoutMs });
} catch (error) {
  if (error instanceof Error && /failed with exit code/.test(error.message)) {
    // Parse the exit code and stderr/stdout tail from the message
    const match = error.message.match(/exit code (\d+)/);
    const exitCode = match ? Number(match[1]) : null;
    console.error(`Command failed (exit ${exitCode}):`, error.message);
    // Retry with adjusted timeout, or report to the caller
  }
  throw error;
}

Prevention

When it happens

Trigger: Any sandbox shell command returning exitCode !== 0 or timing out. This includes: mkdir failing due to permissions, base64 decode failing on corrupt data, tar extraction failing, 'wc -c' failing on a missing file, post-upload commands failing, install commands failing (though install failures are caught and warned, not thrown), and any command exceeding the timeout.

Common situations: The sandbox environment lacks expected tools (e.g., no base64, no dd, no tar). Permission denied on remote paths. Disk full on the sandbox. Network timeouts on provider-backed sandbox RPCs. A post-upload command (e.g., npm install) failing due to missing dependencies. The remote working directory was deleted during a run.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/f425b51782887fff. Report an issue: GitHub.