A runtime fault keeps the output that ran before it - #448
Merged
Merged
Conversation
`break`, `continue`, `return`, and `exit` already carry the output a
block produced before they leave it. A runtime fault propagated as a
bare `anyhow::Error` and dropped that output at every block on the way
up: `echo left && x=$((1/0))` printed only the error, an `if` body lost
what it printed, and a function that faulted became exit 1 with neither
its output nor the real cause, since the stage fold used `to_string()`.
A block that faults now wraps the error in a private carrier holding
its accumulated output, merging with any output an inner block already
attached: if and while conditions and bodies, for and case bodies, both
sides of && and ||, function bodies, `source`, and `.kai` scripts. A
command substitution keeps only stderr, because its stdout was
captured, never printed. The carrier renders exactly as the error it
wraps, so no message text changes.
At the top level, a streaming caller receives the faulting statement's
partial output through `on_output` before the `Err`.
`KernelError::Execution` becomes `{ error, output }` so a non-streaming
caller can read the same output; this breaks `Execution(e)` patterns.
A pipeline stage that faults becomes a failed result holding the
carried output and the `{:#}` cause chain.
Co-Authored-By: DeepSeek V4 Flash <noreply@deepseek.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tobert
added a commit
that referenced
this pull request
Sep 13, 2026
Brings in #445, #447, #450, #446, and #448. Two conflicts, both from additions at the same spot: CHANGELOG.md Fixed bullets (all kept, in merge order) and scheduler/pipeline.rs, where #448's `fault_result` and this branch's `redirects_stdout` were each added after `finalize_scatter_gather_error`. Both helpers are kept; the dispatch site merged cleanly with `fault_result(e)` and the stream-flag restore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tobert
added a commit
that referenced
this pull request
Sep 21, 2026
A spilled background job and one that genuinely exited 3 were the same report. `Job::to_info` copied `result.code` and nothing else, so `did_spill` and `original_code` never reached `JobInfo`, and `failed:3` was the whole answer either way. An embedder reading `jobs --json` had no way to ask whether the 3 was the command's own. `failed:3` stays as the status string — it is the loud signal that GH #212 installed, and replacing it would trade one ambiguity for a compatibility break. The two facts ride alongside it instead. `JobInfo` is `#[non_exhaustive]` with a builder, so the fields are additive, and `jobs --json` serializes `JobInfo` directly, so they appear with no rendering change: ``` {"id":1,"command":"seq 1 5000","status":"failed","exit_code":3,"did_spill":true,"original_code":0} ``` The test that proves it goes through `Job::to_info` rather than building a `JobInfo` by hand — which is how the gap stayed invisible, since every existing `JobInfo` test constructs the struct itself and so cannot miss a field the converter forgets. It runs two jobs under one output limit and separates them on the new fields alone. Second: the test-only `BackendDispatcher` drained an external's stdout into its own ring with no tee into the background job's stream, so no test driven through that dispatcher could observe stream routing at all — a hole directly under the streaming work in #446/#448/#449, in the twin that exists so tests can reach exactly that. It now tees the way the production spawn site does, under the same only-or-last-stage rule, with a test that fails when the tee is removed. Gates: `cargo test --all` clean, `cargo clippy --all --all-targets` zero warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
break,continue,return, andexitalready carry the output a block produced before they leave it. A runtime fault propagated as a bareanyhow::Errorand dropped that output at every block on the way up. A function that faulted became exit 1 with neither its output nor the real cause, because the pipeline stage folded the error withto_string().A block that faults wraps the error in a private carrier holding its accumulated output, merging with any output an inner block already attached. The sites are if and while conditions and bodies, for and case bodies, both sides of
&&and||, function bodies,source, and.kaiscripts. A command substitution keeps only stderr, because its stdout was captured and never printed. The carrier renders exactly as the error it wraps, so no message text changes.At the top level, a streaming caller receives the faulting statement's partial output through
on_outputbefore theErr. A pipeline stage that faults becomes a failed result holding the carried output and the{:#}cause chain.BREAKING:
KernelError::ExecutionbecomesExecution { error, output }so a non-streaming caller can read the same output. AKernelError::Execution(e)pattern no longer compiles; kaijutsu and kaibo do not match on the variant.This stacks on #445.
Co-Authored-By: DeepSeek V4 Flash noreply@deepseek.com
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
🤖 Generated with Claude Code