# 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' | 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).