You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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:

  1. Restart boundary: the restarted shell is a fresh repl::init/repl::start with 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.
  2. ::errorInfo is boot-noise-prone anyway: a caught error still stamps ::errorInfo. tcllib's virtchannel_core/events.tcl (tcl::chan::events) runs catch { 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::start restarts)? 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 errorInfo in 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).