4.2 KiB
G-038 Piped-phase errors inspectable in the restarted interactive shell
Status: proposed
Scope: src/lib/app-punkshell/punkshell.tcl (eof-restart handover), src/modules/punk/repl-999999.0a1.0.tm (error capture at the reporting site and/or teardown), src/modules/punk/repl/codethread-999999.0a1.0.tm (as needed for code-interp access)
Acceptance: 'puts test;error xxxx' | <punkexe> shell then, at the first interactive prompt, a documented command or variable yields the xxxx message, its errorInfo traceback and errorCode from the piped phase, and the first prompt is preceded by a one-line notice that a piped-phase error was retained; the retained record survives boot-time caught-error noise (proven against the motivating transcript, where plain set errorInfo showed virtchannel's can't unset "timer" instead); an error-free piped phase produces no notice and an explicitly-empty record; plain ::errorInfo semantics are otherwise unchanged; verified on both Tcl generations.
Context
Motivating transcript (user, 2026-07-08): 'puts test;error xxxx' | punk902z shell - the
piped phase prints test then the xxxx error, EOF triggers app-punkshell's CONIN$ restart,
and at the interactive prompt set errorInfo shows an unrelated
can't unset "timer": no such variable ... "unset timer" instead of the xxxx traceback.
The user had hoped the piped error would be retained for interactive inspection.
Two mechanisms defeat the natural expectation:
- Restart boundary: the restarted shell is a fresh
repl::init/repl::startwith a new codethread and code interp (app-punkshell do_shell). The piped phase's ::errorInfo lives in run-1's code interp, which is torn down with its codethread. Nothing carries it across. - ::errorInfo is boot-noise-prone anyway: a caught error still stamps ::errorInfo.
tcllib's
virtchannel_core/events.tcl(tcl::chan::events) runscatch { unset timer }as ordinary control flow, and the repl-exec log tee construction (fifo2 stacks during repl::init) triggers it in the fresh code interp - so a brand-new shell's resident ::errorInfo is whatever internal caught error happened last during boot. Restoring the retained error into plain ::errorInfo would be clobbered by the same noise, hence the punk-owned record in the goal statement (or injection strictly after boot completes - but a dedicated record is robust and self-describing).
Approach (design points)
- Capture at the reporting site, not by scraping ::errorInfo at teardown: the repl already
distinguishes command errors when it renders the tcl-err (lowercase
p%) prompt path - that is the natural place to record {message errorInfo errorCode command-string timestamp} for the most recent errored command. Teardown-time capture of the code interp's ::errorInfo is the fallback but inherits the noise problem above. - Handover: the record must cross thread/interp teardown - candidates: tsv (already used by the repl for runchunks), or app-punkshell capturing it from the run-1 exitinfo/donevalue plumbing and seeding the run-2 code interp after init.
- Surface: a documented variable in the code interp (e.g. ::punk::last_pipeline_error) or a small command; the first-prompt notice should be one line and only appear when a retained error exists (piped runs that end cleanly stay quiet - agents/CI pipe scripts constantly).
- Scope question to settle: retain only across the piped->interactive restart, or also
expose "last error of the previous run" generally (subshell exits,
repl::startrestarts)? The goal commits only to the piped->interactive case; wider retention can be recorded here if it falls out naturally. - Note the errorInfo-noise observation stands alone too: even without retention,
set errorInfoin a fresh punk shell reports internals' caught errors. Out of scope to "fix" tcllib, but the dedicated-record design sidesteps it and the doc/help for the new record can mention the caveat.
Notes
- Related: G-036 detail (same eof-restart machinery, where the codethread/interp lifecycle is mapped); G-015 (script subcommand is the agent-facing piped path and already reports errors on stderr with honest exit codes - this goal is about the shell subcommand's interactive restart, a human-facing convenience).