# G-038 Piped-to-interactive restart continues the same session (context preserved) Status: proposed Scope: src/lib/app-punkshell/punkshell.tcl (eof-restart handover), src/modules/punk/repl-999999.0a1.0.tm (eof-restart done-mode that skips codethread teardown), src/modules/punk/repl/codethread-999999.0a1.0.tm (as touched) Acceptance: after `'set ::jjj blah;error xxxx' | shell` reaches the interactive prompt: `set ::jjj` returns blah; the xxxx message, errorInfo traceback and errorCode are inspectable (via preserved ::errorInfo if nothing in the handover overwrites it, else a documented record - the chosen mechanism noted in the detail file); a proc defined and a package required during the piped phase remain available; a cwd change from the piped phase persists; a one-line notice at the restart says the session continued from piped input (mentioning the error when the last piped command errored, quiet otherwise); interactive exit/quit teardown afterwards is clean (the G-036 regression harness still passes); PUNK_PIPE_EOF policy semantics and the script subcommand are unchanged; verified on both Tcl generations. ## Context Reworked 2026-07-08 from a narrower draft ("piped-phase errors inspectable in the restarted interactive shell") after the user identified the general least-surprise violation: - `'set ::jjj blah' | punk902z` -> interactive prompt -> `set ::jjj` fails: no such variable. - `'puts test;error xxxx' | punk902z shell` -> `set errorInfo` at the prompt shows unrelated boot noise (`can't unset "timer"` from tcllib virtchannel_core's `catch { unset timer }`, stamped during the fresh repl::init's fifo2 log-tee construction) instead of xxxx. Cause: app-punkshell's piped-EOF handling (the path that actually runs for pipes, since a pipe is not tcl_interactive) rebuilds the shell from scratch - `do_shell` runs a fresh `repl::init` (new codethread + code interp) and `repl::start`. Everything the piped phase did in the code interp - variables, procs, namespaces, package requires, cwd, errorInfo - is silently discarded. The user's mental model is "my session continues interactively", i.e. the same expectation a tclsh user has typing those commands then continuing. Precedent in the codebase: the repl-internal restart path (`repl::reopen_stdin`) is context-preserving by design - it reuses the live codethread and restarts the repl on the reopened console channel. Its problem is structural (it nests a second repl::start inside the dying repl's event stack, sharing ::repl::done/codethread state - see the G-036 detail file's early analysis of that path's races), not conceptual. ## Approach The caller-driven restart already identified during G-036 as the clean fix direction: 1. `repl::start` gains an eof-restart outcome that returns WITHOUT tearing down the codethread (today's teardown at the end of repl::start unconditionally cancels it - the exit/quit/eof done-values must branch). 2. app-punkshell's eof branch, on deciding to go interactive (existing PUNK_PIPE_EOF policy unchanged), opens CONIN$/SetStdHandle as today but then calls `repl::start` again on the new channel WITHOUT `repl::init` - same codethread, same code interp, session continues. (do_shell currently bakes `repl::init` into the script it runs - the restart invocation needs a variant.) 3. What renews vs persists: - renewed: input channel + repl reader, tcl_interactive, prompt state; the anchored default console object must be rebound to the new channels (known stale-after-reopen comment at repl-999999 ~line 3168; adjacent to G-001/G-011 console-object work). - persists: everything in the codethread/code interp, including `after` timers and fileevents registered by the piped script (a feature, but note it as a behaviour change - a piped script's scheduled work now keeps running into the interactive session; the notice line mitigates surprise). - decide: repl-side editbuf/history fresh or carried (fresh is acceptable; record the choice). 4. Error inspectability likely falls out free: with no re-init there is no boot-noise re-stamping, so the piped phase's ::errorInfo is simply still present. If some handover step does overwrite it, fall back to the superseded draft's mechanism: capture at the repl's error-reporting site (the lowercase p% prompt path) into a punk-owned record (e.g. ::punk::last_pipeline_error) surfaced with the notice line. Note regardless: bare ::errorInfo in a long-lived shell is always subject to later caught-error stamping by internals - the notice line should show the error message itself so the user need not race to inspect. Subshell continuity (user note 2026-07-08, support if practical - not in acceptance): once per-subshell configurations exist (G-008/G-009), a user might reasonably run `'subshell ' | ` and expect the interactive prompt to be IN that subshell, with `quit` unwinding to the initial shell. This does NOT fall out of this goal's changes automatically: continuity preserves code-interp state, but the subshell stack is repl control flow - under the current synchronous nested model the piped `subshell` command nests a repl on the same stdin, and pipe-EOF unwinds the whole subshell stack before the restart decision is made, so the prompt would land in the top shell (with the subshell's interp-side effects intact but the subshell exited). Options: (a) within this goal - capture the active subshell stack at EOF and re-enter it on restart (fights the nesting model); (b) the natural home - G-002's non-nested subshell architecture with console targeting, where pipe-EOF becomes "reattach the reopened console to the innermost active subshell" and quit unwinds normally. Record the outcome here; if deferred to G-002, this note is the cross-reference. Related idea (separate concern, launcher surface): a `-subshell ` flag on the `shell` subcommand as the primary way to start inside a named subshell (candidate for the G-032 punk::args launcher declarations when subshell configs exist). Risks / interactions: - The teardown-mode branching touches exactly the machinery mapped during G-036 (shellthread worker lifecycle, shellfilter::run tail, thread teardown ordering) - the G-036 regression harness is the safety net and is in this goal's acceptance. - shellfilter::run/do_shell wrap the repl in tee stacks per invocation; the restart variant must not double-stack or leak them across the handover. ## Alternatives considered - Standalone piped-error record without session continuity (this goal's original draft) - superseded by the rework: it fixed one symptom (error inspection) while leaving the general context loss (::jjj case) in place, and continuity likely obviates the record. - Keeping fresh-session semantics but documenting them + printing a "new session" notice - rejected as the primary outcome (documentation doesn't restore the lost work), though the notice-line idea is retained within the continuity design. ## Notes - Related: G-036 detail (restart machinery map, teardown races, regression harness); G-002 (non-nested subshell architecture - same family of repl lifecycle decoupling); G-031 (componentized boot); G-001/G-011 (console object rebinding on reopen); G-015 (script subcommand unaffected - it never drops to interactive).