paperclipai/paperclip · error

Process tree has not exited.

Error message

Process tree has not exited.

What it means

After a non-completed process, the finally block issues DELETE with grace_ms=1000 to terminate the process tree. If the termination response reports tree_exited !== true, containment is not proven, so execute() throws this error (which the inner catch converts into CreateosCleanupError). It prevents silently leaving a live process in the sandbox.

Solutions

  1. Destroy the whole sandbox lease to guarantee containment rather than trusting process-level termination.
  2. Increase the grace_ms parameter if the API allows a longer termination window.
  3. Poll process status after DELETE to confirm exit before treating the run as complete.
  4. Investigate the command for processes that spawn detached children or trap signals.

Example fix

// before
if (termination.tree_exited !== true) throw new Error("Process tree has not exited.");
// after
if (termination.tree_exited !== true) {
  await client.json(`${base}/${processId}?grace_ms=5000`, "DELETE", undefined, cleanupSignal);
}
Defensive patterns

Strategy: validation

Try / catch

try {
  const r = await execute(lease, cmd, { signal });
  if (r instanceof CleanupUnconfirmed) await destroyLease(lease);
  return r;
} catch (e) {
  await destroyLease(lease); // containment unproven: reclaim sandbox
  throw e;
}

Prevention

When it happens

Trigger: The DELETE /processes/:id?grace_ms=1000 response returns tree_exited false — the process tree ignored SIGTERM/SIGKILL within the grace period, or the server reports exit asynchronously and the response races termination.

Common situations: Runaway or stuck child processes ignoring termination signals; very short grace_ms (1000ms) insufficient for a large process tree to die; server reporting state before reaping children.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

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

      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],
        }, cleanupSignal).catch(() => undefined);
      }
    }
  }
}

View on GitHub (pinned to 3f1d897a7c)