From 330fcbb356e00674813764f04c0fe3966ed2af82 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Wed, 8 Jul 2026 04:09:28 +1000 Subject: [PATCH] add goal G-038 (proposed): piped-phase errors inspectable in restarted interactive shell Motivating transcript: piped `error xxxx` then interactive `set errorInfo` shows tcllib virtchannel boot noise (caught `unset timer`) instead - the restart boundary discards run-1's code interp, and bare ::errorInfo is clobbered by internal caught errors anyway. Detail file records the two mechanisms and design points (capture at the repl error-reporting site, tsv/exitinfo handover, punk-owned record + one-line first-prompt notice, quiet on clean pipes). Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- GOALS.md | 6 +++ goals/G-038-piped-error-retention.md | 56 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 goals/G-038-piped-error-retention.md diff --git a/GOALS.md b/GOALS.md index 37e871eb..3407ff26 100644 --- a/GOALS.md +++ b/GOALS.md @@ -269,3 +269,9 @@ Scope: src/make.tcl (new or extended step), src/vendorlib_tcl8 + src/vendorlib_t Detail: goals/G-037-vendorlib-vfs-propagation.md Goal: platform-specific vendored binary packages under src/vendorlib_tcl/ reach the kit vfs lib_tcl trees through a make.tcl step instead of hand-copying - updating a vendored package becomes a vendorlib drop plus standard build invocations (motivating case 2026-07-08: tcludp 1.0.12 -> 1.0.13 for the G-036 wedge fix - `libs`, `vfscommonupdate` and `project` all completed while every kit vfs still bundled udp 1.0.12, requiring a manual copy into each vfs lib_tcl9 folder). Acceptance: with a newer package version placed under src/vendorlib_tcl9/, one documented make.tcl invocation updates the participating src/vfs/*/lib_tcl9 trees - installing the new package and removing or explicitly retiring the superseded version (no silent mixed-version provision, per the G-035 concerns) - with punkcheck-tracked provenance; which vfs folders participate is explicitly declared per kit rather than blanket-copied (kit vfs package sets may intentionally differ), with the declaration mechanism recorded (candidate home: the G-024 mapvfs toml); a subsequent `make.tcl project` yields kits loading the new version (provable via the tcludp case: built punk902z reports `package require udp` == 1.0.13 with no udp1.0.12 folder remaining in its vfs); the lib_tcl8 tree gets the same treatment or an explicit exclusion rationale in the goal record. + +### G-038 [proposed] Piped-phase errors inspectable in the restarted interactive shell +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) +Detail: goals/G-038-piped-error-retention.md +Goal: when piped stdin ends and app-punkshell restarts an interactive shell on the console, the last error from the piped phase is retained across the codethread/interp boundary and inspectable at the interactive prompt via a documented punk-owned record - not bare ::errorInfo, which boot-time internal caught errors (e.g. tcllib virtchannel's `catch {unset timer}`) clobber - with a one-line notice at the first prompt when such an error exists. +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. diff --git a/goals/G-038-piped-error-retention.md b/goals/G-038-piped-error-retention.md new file mode 100644 index 00000000..840b0c9e --- /dev/null +++ b/goals/G-038-piped-error-retention.md @@ -0,0 +1,56 @@ +# 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).