{"record":{"id":"48e11701c385ddd9","repo":"nanocoai/nanoclaw","slug":"container-exited-non-zero","errorCode":null,"errorMessage":"Container exited non-zero","messagePattern":"Container exited non-zero","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/drivers/docker-driver.ts","lineNumber":476,"sourceCode":"  ) {}\n\n  async start(): Promise<void> {\n    if (this.#proc) return; // idempotent\n    // `start --attach` is the supervision channel: it exits with the container's\n    // exit code and streams the stderr that explains a boot failure. A container\n    // that dies at boot (unknown provider, missing binary, bad config) explains\n    // itself only here, so keep a tail and surface it at warn on a non-zero exit.\n    const proc = this.cli.start(['start', '--attach', this.name]);\n    this.#proc = proc;\n    proc.onStderr((line) => {\n      log.debug(line, { container: this.name });\n      this.#stderrTail.push(line);\n      if (this.#stderrTail.length > 10) this.#stderrTail.shift();\n    });\n    proc.onExit((code) => {\n      this.#attachExitCode = code;\n      if (!this.#stopping && code !== 0 && code !== null && this.#stderrTail.length > 0) {\n        log.warn('Container exited non-zero', { containerName: this.name, code, stderrTail: this.#stderrTail });\n      }\n      // Every observed terminal transition rides the driver-level stream —\n      // even ends the host requested. Intent filtering is the hub's job.\n      this.emit({ key: this.key, kind: 'terminal' });\n    });\n  }\n\n  async status(): Promise<SessionStatus> {\n    let state: string;\n    try {\n      state = this.cli.run(['inspect', '--format', '{{.State.Status}}|{{.State.ExitCode}}', this.name]).trim();\n    } catch {\n      // `--rm` means an exited container has already been removed; the attach\n      // exit code is the only record of how it ended. A host-requested stop is\n      // not a failure, whatever code the runtime used to end the process.\n      if (!this.#stopping && typeof this.#attachExitCode === 'number' && this.#attachExitCode !== 0) {\n        return {\n          phase: 'failed',","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/drivers/docker-driver.ts#L458-L494","documentation":"A session container process exited with a non-zero code while not being intentionally stopped, and stderr lines were captured. This points at a crash inside the container (agent-runner/provider failure) rather than a host-initiated stop.","triggerScenarios":"Container entrypoint (`bun ...` agent-runner) exits non-zero — provider SDK crash, missing file in the image, OOM kill, or a runtime panic. Logged only when a stderr tail was captured.","commonSituations":"OOM (container memory limits), corrupted image after a partial rebuild, or a provider CLI (claude-code) crashing on startup.","solutions":["Read the stderrTail in the log entry — it names the failing component","Rebuild the image cleanly: prune builder cache then `./container/build.sh`","Check memory limits in container config and host RAM","Retry by sending a message (host respawns the session container)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import { execSync } from 'node:child_process';\nexecSync(`docker inspect nanoclaw-agent:latest`);","typeGuard":null,"tryCatchPattern":"proc.onExit(code => { if (code !== 0 && !stopping) inspectStderrTail(); });","preventionTips":["Build images with a pruned builder cache to avoid corruption","Set adequate container memory limits","Capture stderr tails immediately — container logs are lost after exit (--rm)"],"tags":["docker","container-exit","crash","stderr"],"backgroundTag":"container-nonzero-exit","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}