# G-076 Adopt upstream tcl9 dead-console fix: shared version gate for watchdog and help tcl warning Status: achieved 2026-07-20 Scope: src/modules/punk/lib-999999.0a1.0.tm (punk::lib::check); src/modules/punk/repl-999999.0a1.0.tm (repl::start watchdog arming); src/modules/punk/AGENTS.md; src/tests/modules/punk/lib/testsuites/lib/checkbugs.test Goal: the tcl9 dead-console defect (upstream ticket f10d91c2d3, root-caused in G-039) is surfaced by 'help tcl' via a has_tclbug_* check, and that check and the repl dead-console watchdog arming share a single version gate, so that runtimes at or past the first Tcl version carrying the verified upstream fix neither warn nor arm the watchdog, while all earlier Tcl 9 windows runtimes keep both. Acceptance: 'help tcl' on an affected runtime shows the warning with the f10d91c2d3 link and mitigation note; the applicability decision is a pure facts-in/verdict-out classifier proc with tests (libbug_udp_threadexit_applies precedent); repl::start arms the watchdog only when the same classifier reports the runtime affected, with live-console/piped/8.6 behaviour unchanged; the fixed-in constant is set only after the G-039 kill procedure, re-run with the watchdog disabled on a runtime containing the upstream fix (an official release is not required - a runtime built from upstream sources at or past the fix merge qualifies), shows the repl exiting cleanly via the script-visible eof path - until then every Tcl 9 windows runtime is treated as affected. ## Context G-039 (achieved 2026-07-12, goals/archive/G-039-orphan-console-spin.md) root-caused an orphaned-shell CPU spin to a Tcl 9 core defect pair in win/tclWinConsole.c: (a) ConsoleEventProc drops the error/EOF notification for a dead console so the script-level fileevent never fires; (b) ConsoleReaderThread busy-loops on the persistent error. The script-level mitigation is repl::console_watchdog (punk::repl 0.5.0). The upstream ticket https://core.tcl-lang.org/tcl/tktview/f10d91c2d3 was filed 2026-07-12 and has since been updated upstream with a fix branch in the Tcl repository (observed 2026-07-13). This goal tracks adoption of the eventual released fix: surfacing the defect through the existing 'help tcl' has_tclbug_* auto-discovery, and making the watchdog version-conditional so runtimes carrying the released fix don't warn and don't arm the probe. ## Approach - punk::lib::check gains a pure classifier `tclbug_console_deadspin_applies {platform tclversion}` (facts in, verdict out, testable - same pattern as libbug_udp_threadexit_applies from G-036, achieved - see goals/archive/G-036-tcl9-udp-console-worker-wedge.md) and a `has_tclbug_console_deadspin` wrapper returning the standard buginfo dict (bugref f10d91c2d3, level major, description noting punkshell's watchdog mitigation). - Detection is version-based only: a behavioural probe is impossible (it would require killing a console). The classifier compares against a `tclbug_console_deadspin_fixed_in` namespace variable; empty means "no fixed release known" and every Tcl 9 windows runtime classifies as affected. - repl::start's watchdog arming consults the same classifier as an additional AND-term, so setting the fixed-in constant once flips both the 'help tcl' warning and the watchdog together. - Error direction is safe: a trunk/dev build carrying the fix under an old-looking patchlevel merely arms a harmless 5s read-only probe. - The fixed-in flip is deliberately gated on verification, not on the upstream branch: G-039 found TWO defects, and the watchdog is only redundant if the notification defect (a) is fixed - i.e. the script actually sees EOF on console death. Before setting fixed_in, re-run the G-039 kill procedure (documented in the archived detail file) on the fixed released runtime with the watchdog disabled and confirm the repl exits via the normal fileevent/eof path. If upstream fixes only the spin, the watchdog stays necessary and the check's description changes instead. ## Notes - G-039 (archived) recorded the reproduction/kill procedure and the two-defect mechanism - see goals/archive/G-039-orphan-console-spin.md. Full ticket text with repro scripts: TEMP_REFERENCE/tcl9-dead-console-spin-TICKET-DRAFT.md. - 8.6 remains out of scope (different console driver; per G-039 user decision 2026-07-12). ## Progress ### 2026-07-13 classifier + help tcl surfacing + watchdog gating (initial increment) Landed (punk::lib 0.4.2, punk::repl 0.5.1): - punk::lib::check: `tclbug_console_deadspin_applies {platform tclversion fixed_in}` pure classifier + `has_tclbug_console_deadspin` wrapper (bugref f10d91c2d3, level major, description notes the punk::repl watchdog mitigation) + gate variable `tclbug_console_deadspin_fixed_in` (empty = all Tcl 9 windows affected). - repl::start watchdog arming gained the `[dict get [punk::lib::check::has_tclbug_console_deadspin] bug]` AND-term; console_watchdog argdoc and src/modules/punk/AGENTS.md note updated. - Tests: checkbugs.test +2 (classifier truth table incl. fixed_in comparisons; live-check/classifier consistency + bugref). Verification (2026-07-13, native tclsh 9.0.3 runtests + punk902z kit 9.0.2 in src mode): - lib checkbugs.test 5/5 pass; repl consolebackends.test 3/3 pass; `make.tcl modules` builds clean. - Live check on tclsh 9.0.3: bug=1; setting fixed_in to the running version flips bug=0. - `'help tcl' | punk902z src` (piped, PUNK_PIPE_EOF=exit): major warning displayed with description, mitigation note and f10d91c2d3 hyperlink. ### 2026-07-13 mitigated/mitigation buginfo axis (punk::lib 0.4.3, punk 0.2.6) - buginfo dicts gain an optional axis orthogonal to level: `mitigated` boolean + `mitigation` text. 'help tcl' renders a triggered mitigated check as "warning level: (mitigated)" in subdued grey (term-grey foreground) instead of the level colour, with an indented "mitigated: " block; unmitigated warnings render unchanged. - has_tclbug_console_deadspin populates it: mitigated when punk::repl >= 0.5.0 is available to the runtime (version discovery without loading, udp-check precedent), mitigation text noting the watchdog scope (non-repl console reads remain exposed). Severity stays major. - checkbugs.test extended (axis key presence/boolean/consistency rules incl. the generic every-check sweep); 5/5 pass under native tclsh 9.0.3. Piped `'help tcl' | punk902z src` verified: grey (38;5;8 fg) mitigated rendering with mitigation block and ticket hyperlink. ### 2026-07-20 upstream fix merged and closed; present in TEMP_REFERENCE/tcl9; verification-runtime clarification - Upstream status: ticket f10d91c2d3 closed 2026-07-14. Fix developed on branch bug-f10d91c2 (key checkin b8a27df92a "Include check for error conditions in generating Windows file events", 2026-07-13), merged to core-9-0-branch at 49925e3365 and to trunk at b35f145d82 (both 2026-07-14), with tests/winConsole.test additions. - The fix addresses BOTH G-039 defects, resolving the Approach's "if upstream fixes only the spin" conditional in the favourable direction: (a) notification - the console event path now raises readable/writable when `handleInfoPtr->lastError != 0`, not only on ring-buffer content/space, so the script-level fileevent fires on console death (win/tclWinConsole.c ~1428); (b) spin - the reader-thread loop now waits 50ms on consoleThreadCV per iteration while lastError is set, a bounded tick relying on interp threads to close the channel, instead of busy-looping (~1724). Expected consequence: the watchdog is redundant on fixed runtimes and one fixed_in edit correctly flips both consumers - still requires the kill-procedure confirmation run. - TEMP_REFERENCE/tcl9 (trunk checkout 88cc0220bd, 2026-07-16) contains the fix in its working tree. Patchlevels: this trunk checkout reports 9.1b1; core-9-0-branch head reports 9.0.5 - so the expected fixed_in value is 9.0.5 (first 9.0-line patchlevel carrying the fix), to be confirmed at verification time. Cleanest verification build is core-9-0-branch head (reports the same 9.0.5 the constant would be set to); a trunk 9.1b1 build proves the fix but setting a 9.0-line fixed_in from it is an inference. - User clarification (2026-07-20): the verification runtime need not be an official release - a runtime built locally from upstream sources at or past the fix merge satisfies the gate. Contract wording update proposed separately (proposal-first). - Build-facility status (2026-07-20): cl/nmake not on PATH but VS 2022 Community with the VC x86/x64 toolset is installed (vswhere-confirmed). Also present: zig (C:/zig), berrybrew mingw gcc (no make). Toolchain policy (user, 2026-07-20): zig is the only mechanism approved to become part of the punkshell project; msys/mingw (used for historic builds) and MS toolchains are test/validation only, MS only if all else fails - so the verification build follows the zig path (next entry). ### 2026-07-20 contract wording applied; prior zig build provenance determined - Contract updated per user approval: Goal now reads "the first Tcl version carrying the verified upstream fix" (was "first released Tcl containing"); the Acceptance kill-procedure clause now reads "on a runtime containing the upstream fix (an official release is not required - a runtime built from upstream sources at or past the fix merge qualifies)". - Prior working zig Tcl build determined (original area c:/buildtcl/2024zig; reference snapshot at TEMP_REFERENCE/2024zig): - Last successful build 2024-11-27, outputs build_tcl90/zig-out90main/bin (tclsh90s.exe static, tclsh90szip.exe, tcl90.dll, tcl9thread301.dll, tcl9vfs142.dll, tcl9vlerq41.dll). - Build script build_tcl90/build90.zig; invocation pattern per build.txt: `zig build --build-file build90.zig --prefix zig-out90 [-Doptimize=ReleaseFast] [make-zipfs]` (build.txt's "zig90*.zig" names are stale shorthand for the build90*.zig files). Caveat: build90.zig was edited 2025-08-25 with no build run since (newest .zig-cache write stays 2024-11-27; the dir is not under VCS, so the exact Nov-2024 script state is not recoverable) - the first duplication run validates the current script state. - Zig: 0.14.0-dev.2074+3450809e3 - still the active C:/zig install (installed 2024-10-30, i.e. before the last build; earlier versions parked in C:/zig/_aside*; script comptime floor 0.13.0-dev.8). - Tcl sources: sibling ../tcl90 = clean fossil checkout of trunk fedf9479331d (2024-11-26, TCL_PATCH_LEVEL "9.0.1") from the same shared clone ~/.fossils/tcl.fossil the TEMP_REFERENCE/tcl9 reference checkout uses. build_tcl90/patch/ (tclZipfs / tclMain piperepl / tclCmdIL) is residue of earlier experiments - build90.zig compiles stock ../tcl90/generic sources; no source patching is part of the working recipe. - Companion sources (siblings of build_tcl90): zlib + vqtcl (plain dirs), itcl + tclvfs (clean fossil checkouts), libtommath, tclthread (fossil checkout with one uncommitted win/thread.rc edit - part of the recipe for the thread extension). - TCL_PATCH_LEVEL is compiled from generic/tcl.h (build90.zig passes no -D override; its tcl_patch_level constant feeds naming/paths only) - so a core-9-0-branch head build will genuinely report 9.0.5 to the classifier. - Verification-build plan (user-directed 2026-07-20): (1) duplicate the known-good build exactly - same zig 0.14.0-dev.2074, same tcl90 checkout, current build90.zig, fresh --prefix; (2) only then advance sources: a NEW checkout of core-9-0-branch head from the same clone (already carries the f10d91c2 fix as of its 2026-07-16 sync; leave the TEMP_REFERENCE/tcl9 trunk checkout and the 2024-vintage tcl90 checkout undisturbed) and rebuild; (3) a newer zig only if the exact-version duplication fails. ### 2026-07-20 builds completed: duplication verified, 9.0.5 verification runtime produced - Step 1 SUCCESS (exact duplication, prefix zig-out90dup2026): `zig build --build-file build90.zig --prefix zig-out90dup2026 -Doptimize=ReleaseFast` with active zig 0.14.0-dev.2074 against the untouched tcl90 checkout, then the `install-libraries make-zipfs` steps. Artifacts: tclsh90s.exe + lib/tcl9.0, self-contained tclsh90szip.exe, tcl90.dll + extension dlls. Smoke: both shells report 9.0.1, zip shell has working auto_load and clock/tzdata. The unbuilt 2025-08-25 build90.zig edit is thereby validated as still-good. Step list for reference: install (default), prepare-source ("patch and prep source" - NOT run, stock sources), build-tclvfs, build-vqtcl, build-tclthread, install-tzdata, install-libraries, make-zipfs. make-zipfs's driver tclsh emits a harmless init.tcl warning (mkzip is a C builtin). - Step 2 SUCCESS (9.0.5 runtime, prefix zig-out905): new sibling checkout c:/buildtcl/2024zig/tcl905 = core-9-0-branch tip 1a9c3b9d96 (2026-07-16, descendant of the 49925e3365 fix merge; f10d91c2 fix present; TCL_PATCH_LEVEL "9.0.5"). build905.zig = copy of build90.zig with minimal deltas: tcl_source_folder "../tcl905"; the two tclUuid.h copy-to-source targets switched from literal-constructed "../tcl90" to tcl_source_folder; and (9.0.5-required, see below) `tclsh_exe.mingw_unicode_entry_point = true;`. Smoke: tclsh90s.exe and tclsh90szip.exe both report 9.0.5, tzdata working, tcl9thread301.dll loads (Thread 3.0.1, tsv present) via direct `load` - no pkgIndex.tcl is installed for extensions by this recipe (2024 outputs likewise; punk kits carry their own) so the kill-procedure repl run needs a one-line pkgIndex shim or direct load for Thread. - Fresh-checkout preconditions discovered (both satisfied by stale leftovers in the 2024 tcl90 tree, so the recipe never noticed): - generic/tclEvent.c #includes tclUuid.h (generated by prepare-source's wrapfiletofile from manifest.uuid). tcl905/win/tclUuid.h hand-generated in the exact format (`#define TCL_VERSION_UUID \` + manifest.uuid content). The tcl90 tree's copy is stale (uuid 2047813f.. vs checkout fedf9479..) - harmless, but past builds embedded a stale source uuid. - win/tclsh.rc includes tclsh.exe.manifest, a configure-generated file (tclsh.exe.manifest.in + @TCL_WIN_VERSION@/@MACHINE@). Generated for tcl905 per the tree's own formula ($TCL_VERSION.$TCL_RELEASE_LEVEL. = 9.0.2.905, MACHINE AMD64). Noted in passing: upstream's current manifest.in no longer carries the activeCodePage UTF-8 windowsSettings block the 2024-era template had - possibly relevant to console-encoding assumptions, upstream rationale unchecked. - 9.0.5 source change that broke the recipe (fixed in build905.zig): upstream removed TCL_BROKEN_MAINARGS from win/tclAppInit.c - the mingw path that previously yielded a plain `main` (+setargv). tclAppInit.c now provides `_tmain`, which under the build's -DUNICODE/-D_UNICODE is `wmain`, so linking needs the mingw unicode entry (`lld-link: undefined symbol: WinMain` otherwise, via zig's mingw crtexewin selection). zig 0.14.0-dev.2074's Compile.mingw_unicode_entry_point (= -municode) is the supported switch; build90.zig (9.0.1 sources) needs no change. - User recollections recorded (2026-07-20): - "tweaks relating to windows resource files": confirmed as (a) the uncommitted tclthread win/thread.rc edit - adds `#include "../../tcl90/generic/tcl.h"` so the version resource sees TCL macros zig doesn't provide via include paths. NOTE it hardcodes the tcl90 tree, so the 9.0.5 thread dll's version RESOURCE reflects 9.0.1-era macros - cosmetic metadata only; parameterize when the recipe is productized (G-005 territory). (b) the generated tclsh.exe.manifest precondition above. - piperepl patch recovery intent: build_tcl90/patch/ holds tclMain.c.piperepl.patch + tclMain_piperepl.c (Aug 2024) - a TCLSH_PIPEREPL env-gated tclMain modification enabling return to the standard C-level repl after a kit main.tcl (::tclsh(dorepl)), buffering unconsumed piped input to ::tclsh(inputbuffer) instead of eval'ing it (unless ::tclsh(evalinput)), and reopening stdin from the console (CONIN$ / /dev/tty) on eof. NOT part of the G-076 stock verification runtime; flagged by the user as worth recovering - candidate goal drafted in conversation 2026-07-20. patch/ also holds tclZipfs.patch + tclCmdIL.patch (unexamined). All exist only in the two on-disk copies (c:/buildtcl + git-ignored TEMP_REFERENCE snapshot) - no VCS durability. ### 2026-07-20 G-039 kill procedure passed on the 9.0.5 runtime; fixed_in set; goal achieved Verification (all runs use the G-039 procedure: payload under a dedicated hidden classic conhost, `Stop-Process -Force` on the conhost pid; runtime under test = zig-out905 tclsh90s.exe 9.0.5 static from the 2026-07-20 build; repl loaded from src modules via a plain-tclsh launcher replicating kit src-mode paths): - Control (defect present): the ticket repro (stdin fileevent + heartbeat) on native tclsh 9.0.3 - orphan SURVIVED the conhost kill at 1.57 cores over 10s, one thread permanently Running, readable handler never fired. Harness topology reproduces the defect faithfully. - Core fix (plain script, no punkshell code): same repro on 9.0.5 - the readable handler FIRED and the read surfaced 'broken pipe' as an error (the ticket's expected "EOF or an error" contract); no spin (0.00 cores, all threads waiting). Both upstream defects verified fixed at script level. - Repl adoption gap found and fixed: with the watchdog disarmed, repl_handler received the delivered event but errored at its unguarded full 'chan configure' probe ("couldn't read console mode: broken pipe"), backgrounding the error and leaving the repl idle with its reader inactive - the handler predated script-visible dead consoles. punk::repl 0.5.2: probe guarded; terminal input loss routes through new repl::console_input_lost (the watchdog's teardown, now shared by both paths). - Acceptance run (shipping config - fixed_in=9.0.5 from source, no overrides): app-punkshell interactive repl on the 9.0.5 runtime reported deadspin-check bug=0 and did NOT arm the watchdog; conhost kill -> "|repl> repl_handler: input channel unusable ... finishing repl (eof)" -> app-punkshell "no console available ... (CONIN$: couldn't open \"CONIN$\": broken pipe)" -> process EXITED 0.2s after the kill. Clean script-visible eof path: no traceback, no watchdog involvement (exit far below the 5s probe interval, no watchdog message in the trail). - Gate regression (affected side): same launcher on native tclsh 9.0.3 - bug=1 with fixed_in=9.0.5, watchdog armed and rescued at 1.9s via the shared console_input_lost teardown (probe and message unchanged). Pre-fix runtimes keep the mitigation; 'help tcl' continues to warn there (same buginfo dict drives it). - Suites (native tclsh 9.0.3 runtests): checkbugs/consolebackends/nscurrent 12/12, then the full console+repl+lib families 115/115 (-jobs 8). `make.tcl modules` builds clean (provenance warnings = expected dirty tree). Landed: - punk::lib (pending 0.5.0 entry): check::tclbug_console_deadspin_fixed_in = 9.0.5 - the single edit that flips both consumers ('help tcl' warning and watchdog arming). - punk::repl (pending 0.5.2 entry): repl_handler dead-channel guard + repl::console_input_lost shared teardown. - src/modules/punk/AGENTS.md watchdog note updated (gate set, delivered-event path). Remaining manual items: none. The 9.0.5 verification runtime is the ad-hoc zig build recorded in the earlier 2026-07-20 entries; reproducing it as a tracked suite is G-096 (proposed).