Skip to content

fix(cli): report a killed edge runtime container instead of a lost log stream - #6615

Open
Prashansa-K wants to merge 2 commits into
developfrom
fix/serve-container-killed
Open

Prashansa-K wants to merge 2 commits into
developfrom
fix/serve-container-killed

Conversation

@Prashansa-K

Copy link
Copy Markdown
Contributor

TL;DR

An edge runtime container that gets killed during supabase functions serve — most often by the out-of-memory killer — is retried until the re-attach cap gives up, and then reported as a lost log stream against a container the message claims is still running. This reports the kill instead, and separates a memory-limit kill from one the CLI cannot attribute.

Refs CLI-2427functions serve misreports a killed edge runtime container as a lost log stream (exit 137). This is the 137 gap #6594 left open.

What was wrong

The retry can never succeed. streamContainerLogs treats container exit 137 as retriable, but the container is created with no --restart policy, so once it is killed it stays dead. docker logs -f is respawned, exits, the container is inspected, 137 comes back.

The resulting message says the opposite of what happened. After the cap the command fails with lost the Edge Runtime log stream 5 times; container … is still running. The container is not running — that is the one fact the preceding docker container inspect established.

The classification sends the user the wrong way. EdgeRuntimeLogStreamLostError declares external_service / network with a "rerun with --debug" remediation. An out-of-memory kill is not a network problem, and that remediation leads nowhere.

The retry also contradicts both of its neighbours: apps/cli-go/internal/utils/docker.go maps 137 to ErrContainerKilled with no retry, and db diff treats 137 as a genuine failure.

What changed

137 falls through to EdgeRuntimeContainerCrashedError, which now carries State.OOMKilled from the docker container inspect the code already makes. That call uses --format "{{json .State}}", so this is one more field read in parseContainerState — no format string changes.

Container state error_kind / error_category Suggestion
137, OOMKilled: true user_actionable / resource_limit raise the container runtime's memory allocation
137, OOMKilled: false unknown / unknown none
129/130/131/143 unchanged — the session ends successfully
any other non-zero unchanged — internal_bug / runtime_crash

resource_limit is a new user_actionable category. Both 137 rows get their own fingerprint suffix, so they stay separable from each other and from the untagged bulk of their categories.

Two calls worth reviewing

An out-of-memory kill is not internal_bug / runtime_crash. It is Docker's memory allocation or the user's own function, and the user can act on it. Filing it against the internal-bug counter would be wrong in the direction this work exists to correct.

A non-OOM 137 is unknown, not user_cancelled. Docker sets OOMKilled reliably only for container-limit OOMs; a host or VM out-of-memory kill on macOS lands here with OOMKilled: false. Calling that "the user cancelled" asserts something we do not know. unknown with a named fingerprint keeps it separable so it can be reclassified once there is data on which cause dominates — at the cost of knowingly adding to the unknown bucket.

Behaviour change to be aware of

A session whose container is killed now fails immediately rather than after five re-attaches, and fails with a different tagged error than before. A functions serve & CI step that was passing through the re-attach window will now fail at the kill.

🤖 Generated with Claude Code

Prashansa-K and others added 2 commits September 15, 2026 10:49
Adds a resource_limit category under user_actionable and its
out_of_memory / container_killed fingerprint suffixes, so an edge
runtime container killed for exceeding its memory limit can be
classified separately from an internal bug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Exit 137 re-attached to the log stream until the re-attach cap gave up,
reporting an out-of-memory kill as a lost log stream against a container
the message claimed was still running. `State.OOMKilled` separates a
memory-limit kill, which carries a memory-allocation remediation, from a
kill the CLI cannot attribute to either side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Prashansa-K
Prashansa-K requested a review from a team as a code owner September 15, 2026 05:26
@Prashansa-K Prashansa-K changed the title fix(cli): report a killed edge runtime container instead of a lost log stream (CLI-2427) fix(cli): report a killed edge runtime container instead of a lost log stream Sep 15, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review

Both independent reviews completed. Six distinct findings were adjudicated: five confirmed and one refuted. No critical or major defects were found.

Findings

Severity Location Category Sources Claim
🟡 MINOR apps/cli/src/shared/functions/serve.ts:1424 behavior-change claude Removing the non-OOM exit-137 retry introduces a race where supabase stop can force-kill the container and functions serve fails before the subsequent prune removes it.
🟡 MINOR apps/cli/src/command-internal/docker-suggest.ts:13 correctness codex The remediation overstates OOMKilled as proof that the container exceeded its own configured memory limit.
🟡 MINOR apps/cli/src/shared/functions/serve.ts:1464 error-handling claude When docker logs -f exits non-zero and inspection finds a stopped OOM-killed container, the code reports a generic log-stream error instead of the container crash and memory remediation.
⚪ NIT apps/cli/src/commands/functions/serve/serve.integration.test.ts:2366 test-coverage claude The five-second timeout does not provide the stated retry-regression detection and unnecessarily includes potentially slow command bring-up.
⚪ NIT apps/cli/src/commands/functions/serve/SIDE_EFFECTS.md:75 documentation claude The exit-code documentation incorrectly restricts the memory-limit classification to exit 137 even though the implementation classifies any OOMKilled: true non-zero exit as a resource limit.

Findings outside the diff

  • 🟡 MINOR apps/cli/src/shared/functions/serve.ts:1464 — When docker logs -f exits non-zero and inspection finds a stopped OOM-killed container, the code reports a generic log-stream error instead of the container crash and memory remediation.
Refuted findings (kept for transparency, not posted as review comments)
  • apps/cli/src/shared/telemetry/error-actionability.ts:365 (telemetry): Using suggestion_type: update_config for runtime memory allocation misleadingly categorizes the remediation as a CLI configuration edit.
    Refuted: The closed vocabulary defines UpdateConfig generically, not specifically as config.toml or CLI-owned configuration. Raising the container runtime's memory allocation is a configuration change, and the separate resource_limit error category preserves the cause.

Stats

Claude findings: 5 · Codex findings: 1 · Confirmed: 5 · Refuted: 1 · Uncertain: 0


Models: claude-opus-5 + gpt-5.6-sol · Trigger: auto · Workflow run

This review runs once per PR. A maintainer can request another with a /ai-review comment.

Comment on lines 1424 to 1434
if (state.exitCode === 0) {
return { _tag: "containerExited" } satisfies ContainerLogsEndReason;
}
if (state.exitCode === 137) {
yield* reattach;
continue;
}
return yield* Effect.fail(
new EdgeRuntimeContainerCrashedError({
message: `error running container ${containerId}: exit ${state.exitCode}`,
containerId,
exitCode: state.exitCode,
oomKilled: state.oomKilled,
}),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MINOR · behavior-change · source: claude

Removing the non-OOM exit-137 retry introduces a race where supabase stop can force-kill the container and functions serve fails before the subsequent prune removes it.

Evidence: apps/cli/src/shared/functions/serve.ts:1418-1434 now immediately fails for stopped exit 137, while apps/cli/src/command-internal/docker-remove-all.ts:130-163 waits for docker stop and only then runs container prune.

Suggested fix: After a non-OOM exit 137, briefly re-inspect for removal or otherwise distinguish forced supervisor shutdown before failing.

Comment on lines +2366 to +2373
// A container killed for exceeding its memory limit never comes back, so a
// regression to re-attaching burns the whole cap before failing; bound the
// wait so that shows up as a timeout rather than a slow pass.
const error = yield* functionsServe(baseFlags()).pipe(
Effect.provide(layer),
Effect.timeout(Duration.seconds(5)),
Effect.flip,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ NIT · test-coverage · source: claude

The five-second timeout does not provide the stated retry-regression detection and unnecessarily includes potentially slow command bring-up.

Evidence: apps/cli/src/shared/functions/serve.ts:131 and 145-147 define five 400ms retry sleeps before EdgeRuntimeLogStreamLostError, while apps/cli/src/commands/functions/serve/serve.integration.test.ts:2375 and 2385 already assert the error type and single inspection.

Suggested fix: Remove the timeout, or correct its rationale and give cold command bring-up sufficient headroom.

Comment on lines +75 to +76
| `1` | the edge-runtime container is killed for exceeding its memory limit — exit `137` with `State.OOMKilled` |
| `1` | the edge-runtime container is killed from outside the CLI — exit `137` without `State.OOMKilled` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ NIT · documentation · source: claude

The exit-code documentation incorrectly restricts the memory-limit classification to exit 137 even though the implementation classifies any OOMKilled: true non-zero exit as a resource limit.

Evidence: apps/cli/src/shared/functions/serve.errors.ts:29-35 checks oomKilled before exit code, and apps/cli/src/shared/functions/serve.errors.unit.test.ts:100-111 explicitly verifies OOMKilled: true with exit code 1; SIDE_EFFECTS.md:75-76 describes only exit 137.

Suggested fix: Describe State.OOMKilled as authoritative and exit 137 as the typical exit code.


/** Remediation hint shown when a container is killed for exceeding its memory limit. */
export const SUGGEST_CONTAINER_MEMORY_LIMIT =
"The container was killed for exceeding its memory limit. Raise the memory allocated to your container runtime and try again.";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MINOR · correctness · source: codex

The remediation overstates OOMKilled as proof that the container exceeded its own configured memory limit.

Evidence: apps/cli/src/command-internal/docker-suggest.ts:13 attributes the kill to "its memory limit," and apps/cli/src/command-internal/docker-lifecycle.ts:269-271 derives that conclusion from only the boolean State.OOMKilled, without inspecting any configured limit or broader runtime memory pressure.

Suggested fix: Describe the event as an OOM-killer termination and advise checking both container limits and available host/runtime memory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant