Browse Source

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
master
Julian Noble 1 week ago
parent
commit
330fcbb356
  1. 6
      GOALS.md
  2. 56
      goals/G-038-piped-error-retention.md

6
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<N>/<platform> reach the kit vfs lib_tcl<N> 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/<platform>, 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' | <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.

56
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' | <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).
Loading…
Cancel
Save