Skip to content

Streaming callers see every terminal event their result reports - #446

Merged
tobert merged 2 commits into
mainfrom
fix/streaming-terminal-events
Sep 13, 2026
Merged

Streaming callers see every terminal event their result reports#446
tobert merged 2 commits into
mainfrom
fix/streaming-terminal-events

Conversation

@tobert

@tobert tobert commented Sep 12, 2026

Copy link
Copy Markdown
Owner

execute_with_options_streaming calls on_output once per top-level statement, and kaish -c prints only what on_output delivers. Text that reaches the returned result but not the callback is text that caller never shows. Three paths did that, or ran a program the caller had already cancelled.

An exit statement added its drained stderr to the aggregate but streamed the statement without it:

kaish -c 'exit $(ls /nope; echo 3)'    # printed nothing; now prints the ls error
kaish -c 'echo $(ls /nope; echo 3)'    # already printed it

The watchdog wrote timeout: timed out after ... only into an empty err. A timed-out program that printed any stderr, including sleep: interrupted, exited 124 with no timeout line. The line is now appended after existing stderr, as the timeout builtin does, and streamed, since no statement carries a deadline. Whole-program background jobs receive it through on_output, so their separate timeout handling is removed.

execute_streaming_inner reset the cancel token its caller had just installed. When an embedder or job token fired before the first statement, the reset replaced it and the program ran to completion; echo a; echo b under a cancelled token exited 0. The loop now keeps the installed token and stops at its first checkpoint.

A cancelled call now exits 130 whatever signal ended its child, matching how a timeout exits 124. A cancel that killed a lone external command reported the child's own status, 143 after SIGTERM or 137 after the grace escalated to SIGKILL, because the statement loop's 130 checkpoint runs only between statements. The token decides, not the code, so exit 143 is unchanged. grace_escalation_sigkills_term_trapping_child used 137 as its evidence of escalation; it now expects 130 and keeps the evidence that already proved it, since the child ignores SIGTERM, dies anyway, and not before the grace. If 137 should stay visible to callers, that assertion is the one to revisit.

The new streaming tests use only builtins, so a default cargo test runs them.

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

tobert and others added 2 commits September 12, 2026 14:59
`execute_with_options_streaming` calls `on_output` once per top-level
statement, and `kaish -c` prints only what `on_output` delivers. Three
paths put text in the returned result that never reached the callback,
or ran a program the caller had already cancelled.

An `exit` statement added its drained stderr to the aggregate but
streamed the statement without it, so
`kaish -c 'exit $(ls /nope; echo 3)'` printed no `ls` error while the
same substitution under `echo` did.

The watchdog wrote `timeout: timed out after ...` only into an empty
`err`, so a timed-out program that printed any stderr, including
`sleep: interrupted`, exited 124 with no timeout line. It now appends
the line the way the `timeout` builtin does, and streams it, since no
statement carries a deadline. Whole-program jobs receive it through
`on_output`, so their own timeout plumbing is gone.

`execute_streaming_inner` reset the cancel token its caller had just
installed. When an embedder or job token fired before the first
statement, the reset swapped in a fresh token and the program ran to
completion: `echo a; echo b` under a cancelled token exited 0. The
loop now keeps the installed token and stops at its first checkpoint.

Co-Authored-By: DeepSeek V4 Flash <noreply@deepseek.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`execute_with_options` documents 124 for a timeout and 130 for a
cancellation. A timeout already reported 124 regardless of the child's
signal, but a cancel that killed a lone external command reported the
child's own status: 143 after SIGTERM, 137 after the grace escalated to
SIGKILL. The statement loop's 130 checkpoint runs only between
statements, so a single statement never reached it.

After the run, a cancelled token on a failed, non-timed-out result now
sets 130. The token decides, not the code, so `exit 143` is unchanged.
`kernel_cancel_kills_running_external` drops its 130-or-143 hedge.
`grace_escalation_sigkills_term_trapping_child` asserted 137 as its
evidence of escalation; it now expects 130 and keeps the evidence that
already proved it: the child ignores SIGTERM, dies anyway, and not
before the grace.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tobert
tobert changed the base branch from kaijutsu-async-programs to main September 13, 2026 11:10
@tobert
tobert merged commit a63e97b into main Sep 13, 2026
3 checks passed
tobert added a commit that referenced this pull request Sep 13, 2026
Brings in #445, #447, #450, and #446. CHANGELOG.md conflicted where #446
and this branch both added Fixed bullets; both are kept, in merge order.

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
tobert deleted the fix/streaming-terminal-events branch September 13, 2026 11:59
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