paperclipai/paperclip · warning

CreateOS command was cancelled.

Error message

CreateOS command was cancelled.

What it means

Thrown when the AbortSignal passed to execute() was aborted but the abort was not the configured timeout (which is handled separately by returning a timedOut result). It means the caller explicitly cancelled the CreateOS command. The error is rethrown from the catch block after cleanup bookkeeping.

Solutions

  1. This is expected on cancellation — catch it and treat the command as cancelled, not failed.
  2. If unexpected, audit who owns the AbortSignal and why abort() was called.
  3. Use signal.reason to distinguish cancellation causes before treating this as an error.
  4. Ensure partial stdout/stderr captured before abort are preserved if needed.

Example fix

// before
try { await run(cmd); } catch (e) { reportFailure(e); }
// after
try { await run(cmd); } catch (e) {
  if (e.message === "CreateOS command was cancelled.") return cancelledResult();
  reportFailure(e);
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await execute(lease, cmd, { signal });
} catch (e) {
  if (e.message === "CreateOS command was cancelled.") {
    return { cancelled: true, partialOutput: captured }; // expected path
  }
  throw e;
}

Prevention

When it happens

Trigger: Caller aborts the signal (AbortController.abort()) while the process runs for a reason other than TimeoutError — e.g. user cancellation, upstream task cancellation, or shutdown — and no prior error was already propagating.

Common situations: User cancels a task in the Paperclip board; orchestrator shuts down and cancels in-flight commands; parent request aborted, cascading an abort into the sandbox call.

Related errors


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

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/execute.ts:206

        // Network read failures can resume from the last accepted sequence.
        // Protocol errors must fail closed rather than reconnect past bad data.
        if (!(error instanceof TypeError) || signal.aborted) throw error;
      }
      if (++reconnects > 3) throw new Error("CreateOS process stream ended without an exit status.");
      await delay(250, undefined, { signal });
    }
  } catch (error) {
    if (creationMayHaveSucceeded && !processId) {
      throw new CreateosCleanupError("CreateOS process creation could not be confirmed; destroy the lease before reusing it.");
    }
    if (signal.aborted && signal.reason?.name === "TimeoutError") {
      output.finish();
      return {
        exitCode: null, timedOut: true, stdout: output.stdout, stderr: output.stderr,
        metadata: { processId, outputTruncated: output.truncated },
      };
    }
    if (signal.aborted) throw new Error("CreateOS command was cancelled.");
    throw error;
  } finally {
    const cleanupSignal = AbortSignal.timeout(client.config.timeoutMs);
    // Do not hide a cleanup failure: the host must know containment is unproven.
    try {
      if (processId && !completed) {
        try {
          const termination = await client.json(`${base}/${processId}?grace_ms=1000`, "DELETE", undefined, cleanupSignal);
          if (termination.tree_exited !== true) throw new Error("Process tree has not exited.");
        }
        catch (error) { if (!(error instanceof CreateosApiError && error.status === 404)) throw new CreateosCleanupError("CreateOS command cleanup failed; process termination is unconfirmed."); }
      }
    } finally {
      if (staged && stdinPath) {
        // /files has no delete verb. /exec supplies a bounded, one-shot removal
        // after the managed process finishes, without retaining another record.
        await client.json(`/sandboxes/${id}/exec`, "POST", {
          cmd: "/bin/rm", args: ["-f", "--", stdinPath],

View on GitHub (pinned to 3f1d897a7c)