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
- Read the stderr/stdout tail in the error message to identify the specific shell error.
- If the command timed out, increase the timeoutMs in the CommandManagedRuntimeSpec or per-command timeout.
- Verify the sandbox has the required tools: check that bash/sh, base64, dd, tar, wc are available.
- For post-upload command failures, run the failing command manually in the sandbox to reproduce and debug.
- 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
- Ensure the sandbox has standard POSIX tools: bash/sh, base64, dd, tar, wc, mkdir, rm, mv, chmod.
- Set adequate timeoutMs values—large file operations may need more time on provider-backed sandboxes.
- Validate post-upload commands locally before configuring them in syncIn operations.
- Check sandbox disk space before large file uploads or tar extractions.
- Handle non-zero exits gracefully—some commands (e.g., command -v) use non-zero exit as a signal, not an error.
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
- Could not determine remote file size for ${remotePath}
- Remote file read was truncated for ${remotePath}: ${out.byte
- post-upload command cwd is not a confined absolute POSIX pat
- post-upload command cwd escapes the operation's target root:
- Timed out checking command "${command}" on sandbox target.
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/f425b51782887fff.
Report an issue: GitHub.