7.5 KiB
G-120 9.1-kit script-mode boot pins process std channels: find holder, restore std-slot recycling
Status: achieved 2026-07-25
Scope: src/modules/punk/console-999999.0a1.0.tm; src/modules/punk/repl-999999.0a1.0.tm; src/lib/app-punkscript/; src/vfs/_config/punk_main.tcl
Goal: identify what in the 9.1-kit script-mode boot path retains a registration on the process std channels (keeping their std slots occupied and their OS handles alive across an explicit chan close), determine whether that retention is deliberate console-ownership machinery or a leak, and land the recorded disposition so that closing a std channel frees its slot - restoring Tcl's std-channel recycling - without breaking console abstraction behaviour.
Acceptance: the registration holder is named (module, proc, mechanism) with reproduction evidence; the disposition is recorded here (deliberate-and-documented, or fixed); a script-mode run of the recipe chan close stderr; open NUL w on the current 9.1 kit yields a channel auto-named stderr with working puts stderr, and the console + repl module suites (src/tests/modules/punk/console/, src/tests/modules/punk/repl/) pass.
Context
Surfaced 2026-07-25 by the console-detach launch-mechanism spike (supporting G-101's Tk-app launch story). Tcl installs the first channel created after an explicit std-channel close into the vacated std slot (generic/tclIO.c Tcl_CreateChannel std-slot install, verified in the TEMP_REFERENCE/tcl9 trunk) - this recycling works on stock 9.1b0 (tclsfe-x64.exe), on punk902z (9.0.2) and on punk905 (9.0.5), but NOT on punk91 (9.1b0 + current punk boot layer): there the std OS handle stays alive after chan close (a registration is held somewhere in the boot path), the slot stays occupied, and the new channel gets a fileN name, so puts stderr then fails with "can not find channel named stderr". Consequence beyond cosmetics: in the 9.1 kit a script cannot release/rebind the process std channels, which the detach launch mechanism (G-101 Notes 2026-07-25) would otherwise use for in-place stdio repair.
Ruled out by the spike: command interception (::open/::chan origins are core), the repl interactive-EOF restart path (does not run in script mode), shellfilter stacks (app-punkscript contract has none), the piperepl tclMain patch (punk91's tclsfe runtime is unpatched per G-118 Notes - achieved, see goals/archive/G-118-tclsh-subcommand-review.md).
Approach
Instrument a script-mode boot to snapshot channel registrations, threads and open channels at boot stages (or bisect module loads); the holder must be a Tcl_RegisterChannel-equivalent (global or cross-interp) on a std channel - suspects in load order: punk::console (ownership/registry anchoring, G-007 lineage), punk::repl + codethread/shellthread setup, the app-punkscript boot itself. Confirm by absence: strip the suspected stage and re-run the recipe.
Alternatives considered
- Fold the investigation into G-101 - rejected (user decision 2026-07-25): G-101 is the 8.6 container strategy; the pin is 9.1-kit boot-layer machinery in the console lineage, and 8.6 kits already recycle.
- Accept as deliberate and design around it (relaunch-self only for 9.x) - fallback disposition if the retention proves load-bearing for console ownership; the spike showed relaunch-self already covers the launch requirement, so this goal's fix is about restoring expected Tcl behaviour, not unblocking the launch path.
Notes
- HOLDER (named 2026-07-25): punk::libunknown 0.2.1, proc
zipfs_tm_UnknownHandler, builtin-static shortcircuit block - the static version-probe child interp (fixed nameptest) leaked whenpackage vsatisfies $static_version {*}$argsthrewwrong # argson an UNVERSIONED require (zero requirement args) betweeninterp createandinterp delete. A non-safe child interp holds registrations on the process std channels (Tcl shares stdin/stdout/stderr into it at creation), so the leaked child kept the closed stderr's OS handle alive and its std slot occupied - Tcl_CreateChannel's std-slot install never fires. - Reproduction evidence (2026-07-25, bin/punk91.exe 9.1b0 script mode):
interp children->ptestwithchan names= stderr stdin stdout andinfo loaded {}= {{} Thread}; recipe yieldedfile216195028c0and "can not find channel named stderr". Same-processinterp delete ptestthen recipe -> channel auto-named stderr, puts stderr OK (causality proven). Post-boot re-trigger:package forget thread; package require threadrecreated the leak on demand (package require ddelikewise, while SUCCEEDING via the fallback path); step-through isolatedpackage vsatisfies 3.0.5(zero requirements) as the throwing call - identical arity error on 9.0.5 and 9.1b0, so the punk91-only symptom is runtime-payload-driven: tclsfe 9.1b0 provides Thread as a true static ({}prefix ininfo loaded, hit by boot's unversioned require), while punk905 loads Thread from a dll path (//zipfs:/app/bin/tcl9thread301.dll) so the shortcircuit never fires there. - Kit verification 2026-07-25 (achieved flip evidence): fix promoted through the
gates (make.tcl modules -> bootsupport -> vfscommonupdate -confirm 0, outputs
committed) and kits rebaked (make.tcl vfs, 8 kits installed). Plain-mode
bin/punk91 scriptrecipe on the rebuilt kit: no child interps after boot,chan close stderr; open NUL wyields a channel auto-named stderr, puts stderr OK. punk905 recipe remains green. Console + repl + libunknown suites: 12 files, 105/105 pass (native tclsh90, Tcl 9.0.3). Scope note: the fix landed in punk::libunknown (loaded by the boot path) rather than the Scope-listed console/repl/app-punkscript surfaces - those were the instrumented suspects, all cleared by the investigation. - DISPOSITION: leak (bug), FIXED - not deliberate console-ownership machinery
(the G-007 ownership registry was not involved; the Context ruled-out list
stands). Fix = punk::libunknown 0.2.2: auto-named probe interp deleted in a
try/finally, zero-requirement requires accept any provided static version,
probe errors fall through to the normal tm search instead of aborting the
require. Verified under
bin/punk91 src script: no child interp after boot, recipe yields a channel auto-named stderr with working puts stderr. Suites: libunknown + console + repl (12 files, 105 tests) all pass under native tclsh90 (Tcl 9.0.3). - Related: G-118 (achieved 2026-07-25 - see goals/archive/G-118-tclsh-subcommand-review.md; console-path machinery + piperepl residue territory; its Notes record punk91's runtime as unpatched, ruling out the patch as the holder); G-011 (stderr semantics - a recycled rebound stderr is a consumer of that goal's defined routing); G-090 (shellfilter/shellthread stack lifecycle audit - adjacent channel-lifetime territory); archived G-007 (goals/archive/G-007-console-location-transparency.md - the console ownership registry lineage the holder most likely belongs to).
- Activation freshness 2026-07-25: goals_xref.tcl score G-120 re-run at the proposed->active flip - no relationships beyond the drafting survey below (top unlinked hits G-013/G-044/G-008/G-043 are the already-dismissed repl-feature module-path sharers; no goals drafted in the interval).
- Overlap survey 2026-07-25 (goals_xref.tcl paths over the scope): surfaced G-002/003/008/009/010/011/013/014/034/038/042/043/044 (punk/repl + punk/console module-path sharing - repl-feature goals, no substantive pin relationship), G-077 (app-punkscript), G-031/032/033/114/118 + others (punk_main.tcl boot-config sharing); substantive relations recorded as Related lines above.