fix(stack): include recent service output in startup failure errors - #6791
Conversation
Startup failures reported only stderr, hiding root causes that services print to stdout (e.g. Realtime's Postgrex connection errors). Keep a bounded tail of the last 20 lines of combined stdout/stderr, including an unterminated last line, and append it to both non-zero exit and timeout errors. Startup output is now published to the service logs as it arrives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Superseded by a newer AI review
🤖 AI Review
The startup output change improves diagnostics, but the retained output is not fully bounded, and later stdout can evict a useful stderr error. The timeout duration is also duplicated. All four reported findings were verified and merged into three distinct findings.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟡 MINOR | packages/stack/src/services/ProcessRecipe.ts:238 |
resource-usage |
claude+codex | A newline-free startup output stream can grow the partial-line buffer without limit and produce an arbitrarily large error message. |
| 🟡 MINOR | packages/stack/src/services/ProcessRecipe.ts:220 |
error-handling |
codex | Later stdout lines can evict the stderr message explaining a startup failure. |
| ⚪ NIT | packages/stack/src/services/ProcessRecipe.ts:251 |
maintainability |
claude | The startup timeout duration is duplicated in the timer and error text, so future edits could make them disagree. |
Stats
Claude findings: 2 · Codex findings: 2 · Confirmed: 3 · Refuted: 0 · Uncertain: 0
Models: claude-opus-5-5 + gpt-6-sol · Trigger: auto · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
…ailure-output-08dd39
Later stdout could evict the stderr error from a shared tail, and a newline-free stream could grow the retained line without limit. Keep a 20-line tail per stream, clip each retained line to its last 1000 characters, and derive the timeout message from the timer's constant. The test fake also implements the discard/kill process members from develop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
/ai-review |
There was a problem hiding this comment.
🤖 AI Review
The startup output change has two confirmed low-impact issues: collected output is omitted when a stream or exit-code effect fails, and clipping can split a Unicode surrogate pair. Codex's independent review reported no findings.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟡 MINOR | packages/stack/src/services/ProcessRecipe.ts:265 |
error-handling |
claude | If a startup output stream or the exit-code effect fails, the error omits output collected before the failure. |
| ⚪ NIT | packages/stack/src/services/ProcessRecipe.ts:213 |
correctness |
claude | Clipping a startup output line can split a UTF-16 surrogate pair, leaving an unpaired surrogate in the error message. |
Stats
Claude findings: 2 · Codex findings: 0 · Confirmed: 2 · Refuted: 0 · Uncertain: 0
Models: claude-opus-5-5 + gpt-6-sol · Trigger: manual · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jgoux
left a comment
There was a problem hiding this comment.
The new startup test fixture fails the package type check because it is missing a required ContainerRuntime member.
…ailure-output-08dd39
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…08dd39' into avallete/startup-failure-output-08dd39
Summary
When a stack service's startup step fails (for example Realtime's migration step), the error now shows the last 20 lines the process printed on each of stdout and stderr. Before, it showed only stderr, which was usually the final exception without its cause. A startup step that hangs now also reports its recent output, including a last line that doesn't end in a newline, instead of a bare timeout.
Why
In #6775,
supabase startunderSUPABASE_EXPERIMENTAL_STACK=1failed with only Realtime's final Ecto pool-timeout exception:The actual cause went to stdout on every connection attempt and was never shown:
supabase stack logscouldn't recover it either, because nothing is retained after a failed start.Before
After
flowchart LR P[startup process<br/>e.g. realtime prepare] -->|stdout chunks| L[published to service logs<br/>as they arrive] P -->|stderr chunks| L P -->|stdout lines| RO[stdout tail<br/>last 20 non-blank lines] P -->|stderr lines| RE[stderr tail<br/>last 20 non-blank lines] RO --> R{startup result} RE --> R R -->|exit ≠ 0| M["❌ realtime startup exited with 1<br/>Recent stdout:<br/>[error] Postgrex ... :enetunreach<br/>Recent stderr:<br/>** (DBConnection.ConnectionError) ..."] R -->|hangs > 60s| T["❌ realtime startup timed out after 60 seconds<br/>Recent stdout: ...<br/>Recent stderr: ..."]What changed
awaitStartupinpackages/stack/src/services/ProcessRecipe.tssplits stdout and stderr into lines and keeps a separate tail of the last 20 non-blank lines for each, so later stdout noise can't push out the stderr error.Recent stdout:/Recent stderr:sections for whichever streams printed something.Linked issue
Part of #6775
🤖 Generated with Claude Code