Browse Source

G-036 achieved: tcludp thread-exit bug detection (punk::lib 0.3.0 has_libbug_udp_threadexit, surfaced via 'help tcl')

Completes G-036's remaining acceptance item - version-based detection of the
vulnerable combination (tcludp < 1.0.13 on Tcl 9 Windows: the per-thread exit
handler closes the process-global sockListLock/waitForSock events, silently
freezing every other udp-using thread's event loop after any udp-loaded thread
exits; root-caused and fixed-by-upgrade in 0.4.3).

- punk::lib 0.3.0: has_libbug_udp_threadexit gathers live facts (loaded udp
  version, else best available registered version discovered without loading
  the binary via an unsatisfiable package require triggering the index scan)
  and delegates the verdict to the pure classifier
  libbug_udp_threadexit_applies (facts in, verdict out - testable). has_libbug_*
  is the new check family for bundled/vendored library bugs; buginfo dicts may
  carry a full 'url' reference key for non tcl-core trackers.
- punk 0.2.1: 'help tcl' scans has_libbug_* alongside has_tclbug_*, renders the
  url key when present, and no longer errors on a triggered check carrying a
  reference without a description (latent unset-indent fix).
- new checkbugs.test: classifier combination matrix, live-check dict shape and
  classifier consistency, and a buginfo-contract test across all existing
  check procs.
- verified: current kit (udp 1.0.13) reports bug=0 with no warning; a simulated
  triggered has_libbug_* check renders description + url in the help tcl
  warning block. Suites: punk/lib 21 pass, punk/ns 26 pass.
- G-036 flipped to achieved 2026-07-08 (index acceptance REMAINING annotated
  DONE; detail file gains the Detection section). Open non-gating decisions
  recorded in the detail file: punk8win (8.6) kit udp 1.0.12 swap; optional
  upstream tickets for residual tcludp trunk weaknesses.
- project 0.4.7 (CHANGELOG entry)

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 7 days ago
parent
commit
48dccbe8c1
  1. 4
      CHANGELOG.md
  2. 4
      GOALS.md
  3. 26
      goals/G-036-tcl9-udp-console-worker-wedge.md
  4. 2
      punkproject.toml
  5. 17
      src/modules/punk-999999.0a1.0.tm
  6. 3
      src/modules/punk-buildversion.txt
  7. 50
      src/modules/punk/lib-999999.0a1.0.tm
  8. 3
      src/modules/punk/lib-buildversion.txt
  9. 79
      src/tests/modules/punk/lib/testsuites/lib/checkbugs.test

4
CHANGELOG.md

@ -5,6 +5,10 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.4.7] - 2026-07-08
- G-036 completed (flipped to achieved): `punk::lib` 0.3.0 adds `punk::lib::check::has_libbug_udp_threadexit` — version-based detection of the tcludp < 1.0.13 thread-exit bug on Tcl 9 Windows (the per-thread exit handler closes process-global event handles, silently freezing every other udp-using thread's event loop after any udp-loaded thread exits; root-caused and fixed-by-upgrade earlier in 0.4.3). `has_libbug_*` is the new check family for bundled/vendored library bugs; `help tcl` (punk 0.2.1) now scans it alongside `has_tclbug_*`, supports a full `url` reference key for non tcl-core trackers, and no longer errors on a triggered check carrying a reference without a description. Detection is loaded-version based, falling back to the best available registered version without loading the binary. New checkbugs.test covers the classifier matrix, the live check's dict shape/consistency, and a contract test over all existing check procs. Open decisions recorded in the G-036 detail: punk8win (8.6) kit's udp 1.0.12 swap; optional upstream tickets for residual tcludp trunk weaknesses.
## [0.4.6] - 2026-07-08
- punk::ns 0.1.1: five development trace `puts` removed from the command doc-lookup machinery — "PROC auto def"/"ENSEMBLE auto def" from generate_autodef were emitted on STDOUT and polluted `i`/`s` output in script/exec contexts (visible to agents piping `i <cmd>` per the G-015 pattern); "skipping to documented subcommand", "cmd_traverse ensembleparam" and the "cmd_traverse 10 ... review" fallthrough note were stderr noise on the space-form-id, ensemble-parameter and undocumented-tail traverse paths. No functional change.

4
GOALS.md

@ -261,11 +261,11 @@ Detail: goals/G-035-mixed-tm-pkgindex-provision.md
Goal: the behaviour when the same package is provided both as a .tm module and as a pkgIndex.tcl-based library - same or differing versions, under the standard package unknown, punk::libunknown and punk::packagepreference - is characterised by committed tests, and the currently informal working rule ("avoid mixing provision forms for one package - unexpected behaviour even with libunknown's improvements") is either substantiated with the specific failure modes named in AGENTS.md guidance, or retired if the characterisation shows the machinery now handles mixing predictably.
Acceptance: a committed test suite (extending src/tests/modules/punk/libunknown/testsuites/) characterises at least: same name+version provided via .tm and via pkgIndex.tcl (which registration wins, and whether it is deterministic across scan-trigger orderings) under the standard scanner, under punk::libunknown, and with punk::packagepreference active; differing versions across the two forms (version selection integrity including package prefer latest, and whether the losing form's registration lingers); re-registration effects (package forget then re-require crossing forms); surprising-but-accepted behaviours are pinned with GAP/known-quirk comments (the fossilmove characterization pattern), outright bugs fixed or filed as goals; the resulting do/don't guidance lands in src/modules/AGENTS.md and src/lib/AGENTS.md naming the characterised failure modes (or explicitly lifting the avoid-mixing rule if unwarranted).
### G-036 [active] Root-cause the Tcl 9 console+udp worker-thread event-loop wedge; minimal repro for possible upstream reporting
### G-036 [achieved 2026-07-08] Root-cause the Tcl 9 console+udp worker-thread event-loop wedge; minimal repro for possible upstream reporting
Scope: src/modules/shellthread-999999.0a1.0.tm, src/bootsupport/modules/shellthread-1.6.2.tm, src/modules/shellfilter-999999.0a1.0.tm (as characterised - no product-code changes required by this goal; the punkshell mitigations are separate fixes)
Detail: goals/G-036-tcl9-udp-console-worker-wedge.md
Goal: the Tcl 9-only wedge - a worker thread that has used a tcludp syslog socket stops servicing its event queue (timers, thread::send) when the punkshell process is console-attached, the proximate trigger of the piped-stdin exit/quit hang - is root-caused to a named component (Tcl 9 Windows console driver, tcludp, Thread extension, or a specific interaction) with the smallest demonstrating repro, so the user can decide on and manually file an upstream report.
Acceptance: (reworked 2026-07-08 after the root cause was found) the wedge mechanism is identified and written up in the detail file - DONE: bundled tcludp 1.0.12's Windows per-thread UDP_ExitProc closes the process-global synchronization events, proven by dump handle-table analysis plus a live CloseHandle breakpoint and confirmed by the upstream 1.0.12->1.0.13 diff, which already fixes it (so no upstream report is required; the standalone minimal repro originally required here is waived as moot by the user); the tcl9 kits bundle tcludp >= 1.0.13 with the in-context batch harness baseline resolved - DONE 2026-07-08 (run-2 syslog workers alive vs the 4/4-wedged 1.0.12 baseline); REMAINING: a has_bug-style detection in the punkshell check machinery (in the vein of punk::lib::check::has_tclbug_* / punk::console::check::has_bug_*, surfaced through the same reporting as 'help tcl'/'help console') reports the vulnerable combination - simple version-based detection (loaded/bundled tcludp < 1.0.13 on Tcl 9 Windows) is sufficient, no behavioural probe needed; loose-end decisions (punk8win 8.6 kit's udp 1.0.12 swap; optional upstream tickets for residual tcludp trunk weaknesses) recorded in the detail file when made.
Acceptance: (reworked 2026-07-08 after the root cause was found) the wedge mechanism is identified and written up in the detail file - DONE: bundled tcludp 1.0.12's Windows per-thread UDP_ExitProc closes the process-global synchronization events, proven by dump handle-table analysis plus a live CloseHandle breakpoint and confirmed by the upstream 1.0.12->1.0.13 diff, which already fixes it (so no upstream report is required; the standalone minimal repro originally required here is waived as moot by the user); the tcl9 kits bundle tcludp >= 1.0.13 with the in-context batch harness baseline resolved - DONE 2026-07-08 (run-2 syslog workers alive vs the 4/4-wedged 1.0.12 baseline); DONE 2026-07-08 (punk::lib 0.3.0 has_libbug_udp_threadexit, surfaced via 'help tcl' in punk 0.2.1): a has_bug-style detection in the punkshell check machinery (in the vein of punk::lib::check::has_tclbug_* / punk::console::check::has_bug_*, surfaced through the same reporting as 'help tcl'/'help console') reports the vulnerable combination - simple version-based detection (loaded/bundled tcludp < 1.0.13 on Tcl 9 Windows) is sufficient, no behavioural probe needed; loose-end decisions (punk8win 8.6 kit's udp 1.0.12 swap; optional upstream tickets for residual tcludp trunk weaknesses) recorded in the detail file when made (open as of 2026-07-08 - non-gating).
### G-037 [proposed] Propagate platform vendor libraries into kit vfs lib_tcl trees via make.tcl
Scope: src/make.tcl (new or extended step), src/vendorlib_tcl8 + src/vendorlib_tcl9 (sources), src/vfs/<kit>.vfs/lib_tcl8 + lib_tcl9 (targets), punkcheck tracking

26
goals/G-036-tcl9-udp-console-worker-wedge.md

@ -1,6 +1,6 @@
# G-036 Root-cause the Tcl 9 console+udp worker-thread event-loop wedge; minimal repro for possible upstream reporting
Status: active
Status: achieved 2026-07-08
Scope: src/modules/shellthread-999999.0a1.0.tm, src/bootsupport/modules/shellthread-1.6.2.tm, src/modules/shellfilter-999999.0a1.0.tm (as characterised - no product-code changes required by this goal; the punkshell mitigations are separate fixes)
Acceptance: (reworked 2026-07-08 after the root cause was found) the wedge mechanism is identified and written up in the detail file - DONE: bundled tcludp 1.0.12's Windows per-thread UDP_ExitProc closes the process-global synchronization events, proven by dump handle-table analysis plus a live CloseHandle breakpoint and confirmed by the upstream 1.0.12->1.0.13 diff, which already fixes it (so no upstream report is required; the standalone minimal repro originally required here is waived as moot by the user); the tcl9 kits bundle tcludp >= 1.0.13 with the in-context batch harness baseline resolved - DONE 2026-07-08 (run-2 syslog workers alive vs the 4/4-wedged 1.0.12 baseline); REMAINING: a has_bug-style detection in the punkshell check machinery (in the vein of punk::lib::check::has_tclbug_* / punk::console::check::has_bug_*, surfaced through the same reporting as 'help tcl'/'help console') reports the vulnerable combination - simple version-based detection (loaded/bundled tcludp < 1.0.13 on Tcl 9 Windows) is sufficient, no behavioural probe needed; loose-end decisions (punk8win 8.6 kit's udp 1.0.12 swap; optional upstream tickets for residual tcludp trunk weaknesses) recorded in the detail file when made.
@ -279,7 +279,29 @@ vendorlib_tcl<N> platform libs into kit vfs lib_tcl<N> trees - gap recorded as g
punk8win.vfs (8.6) still bundles udp 1.0.12 - same buggy code, immunity unexplained
(pending decision whether to swap in 1.0.13's udp1013t.dll for the 8.x kit too).
Loose ends / optional follow-ups:
## Detection (2026-07-08 - the final acceptance item, goal flipped to achieved)
punk::lib 0.3.0 adds the check pair in ::punk::lib::check:
- `libbug_udp_threadexit_applies {udpversion platform tclversion}` - pure classifier
(facts in, verdict out; covered by a combination-matrix test in
src/tests/modules/punk/lib/testsuites/lib/checkbugs.test). Vulnerable = udp version
non-empty and < 1.0.13, platform windows, Tcl >= 9 (8.6 excluded per this goal's
acceptance - its apparent immunity remains unexplained, see Context).
- `has_libbug_udp_threadexit` - gathers live facts: [package provide udp] when loaded,
otherwise the best available registered version discovered WITHOUT loading the binary
(an unsatisfiable `package require udp 999999` triggers the index scan, then
[package versions udp]). Returns the standard buginfo dict (bug/description/level
medium/libversion) plus a full `url` reference (https://core.tcl-lang.org/tcludp -
tcludp is not a tcl-core tktview target).
Surfacing: `help tcl` (punk 0.2.1 helptopic::tcl handler) scans has_libbug_* alongside
has_tclbug_* and renders the `url` key when present (bugref remains the tcl-core tktview
shorthand). Verified: current punk902z kit (udp 1.0.13) reports bug=0 with no warning;
a simulated triggered has_libbug_* check renders the warning block with description and
url; checkbugs.test also pins the buginfo dict contract across all existing check procs.
Loose ends / optional follow-ups (open as of 2026-07-08 - non-gating, record decisions here when made):
- Check which tcludp the punksys (8.6) kit bundles and why 8.6 appeared immune (different udp
version, different exit-handler ordering, or fast-fail luck).
- Residual weaknesses still present in tcludp trunk, candidates for polite upstream tickets:

2
punkproject.toml

@ -1,3 +1,3 @@
[project]
name = "punkshell"
version = "0.4.6"
version = "0.4.7"

17
src/modules/punk-999999.0a1.0.tm

@ -8570,7 +8570,7 @@ namespace eval punk {
punk::args::define {
@id -id ::punk::helptopic::tcl
@cmd -name "help tcl" -summary "Tcl version warnings." -help "Show the running Tcl patchlevel and build-info, with warnings for known Tcl bugs affecting this interpreter (as detected by the punk::lib::check::has_tclbug_* checks)."
@cmd -name "help tcl" -summary "Tcl version warnings." -help "Show the running Tcl patchlevel and build-info, with warnings for known Tcl bugs affecting this interpreter and known bugs in bundled library packages (as detected by the punk::lib::check::has_tclbug_* and has_libbug_* checks)."
@values -min 0 -max 0
}
proc tcl {context args} {
@ -8580,8 +8580,12 @@ namespace eval punk {
catch {
append text \n "Tcl build-info: [::tcl::build-info]"
}
#generate warningblocks for each triggered Tcl bug in namespace ::punk::lib::check
set bugcheck_procs [info procs ::punk::lib::check::has_tclbug*]
#generate warningblocks for each triggered bug check in namespace ::punk::lib::check
#has_tclbug_* = Tcl core bugs; has_libbug_* = bundled/vendored library bugs (e.g. tcludp - G-036)
set bugcheck_procs [concat\
[info procs ::punk::lib::check::has_tclbug*]\
[info procs ::punk::lib::check::has_libbug*]\
]
foreach bp $bugcheck_procs {
set buginfo [$bp]
if {[dict get $buginfo bug]} {
@ -8595,12 +8599,15 @@ namespace eval punk {
major {set highlight [punk::ansi::a+ red bold]}
default {set highlight ""}
}
set indent " "
append warningblock \n $highlight "warning level: $level $bp triggered."
if {[dict exists $buginfo description]} {
set indent " "
append warningblock \n "[punk::lib::indent [dict get $buginfo description] $indent]"
}
if {[dict exists $buginfo bugref] && [dict get $buginfo bugref] ne ""} {
if {[dict exists $buginfo url] && [dict get $buginfo url] ne ""} {
#full reference url (e.g. non tcl-core trackers such as tcludp)
append warningblock \n "${indent}see [punk::ansi::hyperlink [dict get $buginfo url]]"
} elseif {[dict exists $buginfo bugref] && [dict get $buginfo bugref] ne ""} {
set bugref [dict get $buginfo bugref]
append warningblock \n "${indent}see [punk::ansi::hyperlink https://core.tcl-lang.org/tcl/tktview/$bugref]"
}

3
src/modules/punk-buildversion.txt

@ -1,4 +1,5 @@
0.2.0
0.2.1
#First line must be a semantic version number
#all other lines are ignored.
#0.2.1 - 'help tcl' warning scan extended to the has_libbug_* check family (bundled/vendored library bugs, e.g. the G-036 tcludp detection) alongside has_tclbug_*; buginfo 'url' key supported for reference links to non tcl-core trackers; fixed latent unset-indent error when a triggered check had a bugref/url but no description
#0.2.0 - help system restructured onto a topic registry (::punk::helptopic: register/resolve, per-topic handler procs each with a punk::args definition); ::punk::help and ::punk::help_chunks punk::args definitions (re)generated from the registry so 'i help' / 'i help <topic>' render documented usage; 'help topics' derived from the registry; command-fallthrough and no-arg overview output unchanged; 'help env' degrades cleanly when punk::config is not initialised; help table objects destroyed after printing (leak fix)

50
src/modules/punk/lib-999999.0a1.0.tm

@ -250,6 +250,56 @@ tcl::namespace::eval punk::lib::check {
return [dict create bug $bug bugref e38dc74e2 description $description level medium]
}
#has_libbug_* procs report bugs in bundled/vendored library packages rather than the Tcl core.
#They are surfaced through the same 'help tcl' warning report as the has_tclbug_* checks.
#pure classifier, separated for testability - facts in, verdict out
proc libbug_udp_threadexit_applies {udpversion platform tclversion} {
if {$udpversion eq ""} {
return 0 ;#no udp package known - nothing vulnerable to load
}
if {$platform ne "windows"} {
return 0
}
if {![package vsatisfies $tclversion 9]} {
#tcl 8.6 exhibited apparent immunity in the G-036 investigation (unexplained - see goal detail)
#detection deliberately scoped to Tcl 9 per G-036 acceptance
return 0
}
return [expr {[package vcompare $udpversion 1.0.13] < 0}]
}
proc has_libbug_udp_threadexit {} {
#tcludp <= 1.0.12 on Windows: the per-thread exit handler UDP_ExitProc closes the
#PROCESS-GLOBAL sockListLock/waitForSock event handles. The first udp-loaded thread to
#exit corrupts the lock for every other udp-loaded thread - their event loops block
#forever in WaitForSingleObject on a dead or recycled handle (timers and thread::send
#unserviced). In punkshell the visible casualty is syslog log workers silently freezing
#(the original exit/quit hang this caused is separately mitigated). Root-caused
#2026-07-08 - see goal G-036 detail. Fixed upstream in tcludp 1.0.13 (per-thread
#UdpThreadExitProc deletes only the event source; ExitSockets closes the events at
#process exit).
#Version-based detection only (per G-036 acceptance) - no behavioural probe.
set udpversion [package provide udp]
if {$udpversion eq ""} {
#not loaded - determine what version would be provided, without loading the binary:
#an unsatisfiable require triggers the package unknown scan (registering ifneeded
#scripts) then fails before any dll load.
catch {package require udp 999999}
set available [package versions udp]
if {[llength $available]} {
set udpversion [lindex [lsort -command {package vcompare} $available] end]
}
}
set bug [libbug_udp_threadexit_applies $udpversion $::tcl_platform(platform) [package provide Tcl]]
if {$udpversion eq ""} {
set verinfo "no udp package found"
} else {
set verinfo "udp $udpversion"
}
set description "tcludp < 1.0.13 on Tcl 9 Windows ($verinfo): the udp package's per-thread exit\nhandler closes process-global event handles - after any udp-using thread exits, every\nother udp-using thread's event loop can freeze silently (e.g. syslog log workers stop).\nUpgrade the bundled/available udp package to 1.0.13+ (see goal G-036)."
return [dict create bug $bug bugref "" url https://core.tcl-lang.org/tcludp description $description level medium libversion $udpversion]
}
proc has_tclbug_safeinterp_compile {{show 0}} {
#ensemble calls within safe interp not compiled
#https://core.tcl-lang.org/tcl/tktview/1095bf7f756f9aed6bde

3
src/modules/punk/lib-buildversion.txt

@ -1,6 +1,7 @@
0.2.1
0.3.0
#First line must be a semantic version number
#all other lines are ignored.
#0.3.0 - new: punk::lib::check::has_libbug_udp_threadexit + libbug_udp_threadexit_applies classifier - version-based detection of tcludp < 1.0.13 on Tcl 9 Windows (per-thread exit handler closes process-global event handles; G-036). has_libbug_* is the new check family for bundled/vendored library bugs, surfaced through 'help tcl' alongside has_tclbug_*; buginfo dicts may carry a full 'url' reference key (non tcl-core trackers)
#0.2.1 - call-site update for punk::console 0.6.0 tsv array rename: console -> punk_console (is_raw); no behaviour change
#0.2.0 - new: snapshot_package_paths proc returns a script string reproducing tm list, auto_path, and package prefer for use in thread::create init scripts
#0.2.0 - extended: interp_sync_package_paths now propagates package prefer and supports optional -libunknown 1 flag for epoch copy + libunknown init

79
src/tests/modules/punk/lib/testsuites/lib/checkbugs.test

@ -0,0 +1,79 @@
package require tcltest
package require punk::lib
#Tests for the punk::lib::check bug-check machinery - added 2026-07-08 with the G-036
#tcludp detection (has_libbug_udp_threadexit: tcludp < 1.0.13 on Tcl 9 Windows closes
#process-global event handles at thread exit - version-based detection per G-036).
namespace eval ::testspace {
namespace import ::tcltest::*
variable common {
set result ""
}
test libbug_udp_threadexit_classifier {version/platform/tclversion combinations for the tcludp thread-exit bug}\
-setup $common -body {
#vulnerable: udp < 1.0.13, windows, tcl 9
lappend result [punk::lib::check::libbug_udp_threadexit_applies 1.0.12 windows 9.0.2]
lappend result [punk::lib::check::libbug_udp_threadexit_applies 1.0.11 windows 9.1]
#fixed versions
lappend result [punk::lib::check::libbug_udp_threadexit_applies 1.0.13 windows 9.0.2]
lappend result [punk::lib::check::libbug_udp_threadexit_applies 1.0.14 windows 9.0.2]
#not windows
lappend result [punk::lib::check::libbug_udp_threadexit_applies 1.0.12 unix 9.0.2]
#tcl 8.6 (apparent immunity - detection scoped to tcl 9 per G-036 acceptance)
lappend result [punk::lib::check::libbug_udp_threadexit_applies 1.0.12 windows 8.6.16]
#no udp package known
lappend result [punk::lib::check::libbug_udp_threadexit_applies "" windows 9.0.2]
}\
-cleanup {
}\
-result [list 1 1 0 0 0 0 0]
test has_libbug_udp_threadexit_shape {the live check returns the standard buginfo dict shape}\
-setup $common -body {
set buginfo [punk::lib::check::has_libbug_udp_threadexit]
foreach key {bug description level url} {
lappend result [dict exists $buginfo $key]
}
lappend result [string is boolean -strict [dict get $buginfo bug]]
lappend result [expr {[dict get $buginfo level] in {minor medium major}}]
#consistency: the live verdict must match the classifier applied to the reported facts
lappend result [expr {
[dict get $buginfo bug] == [punk::lib::check::libbug_udp_threadexit_applies\
[dict get $buginfo libversion] $::tcl_platform(platform) [package provide Tcl]]
}]
}\
-cleanup {
}\
-result [list 1 1 1 1 1 1 1]
test has_bugcheck_procs_return_standard_dict {every has_tclbug_*/has_libbug_* check returns a dict with a boolean bug key and a level}\
-setup $common -body {
set checkprocs [concat\
[info procs ::punk::lib::check::has_tclbug*]\
[info procs ::punk::lib::check::has_libbug*]\
]
lappend result [expr {[llength $checkprocs] > 0}]
set badprocs [list]
foreach bp $checkprocs {
if {[catch {$bp} buginfo]} {
lappend badprocs [list $bp error $buginfo]
continue
}
if {![dict exists $buginfo bug] || ![string is boolean -strict [dict get $buginfo bug]]} {
lappend badprocs [list $bp badbugkey]
continue
}
if {[dict exists $buginfo level] && [dict get $buginfo level] ni {minor medium major}} {
lappend badprocs [list $bp badlevel [dict get $buginfo level]]
}
}
lappend result $badprocs
}\
-cleanup {
}\
-result [list 1 {}]
}
tcltest::cleanupTests ;#needed to produce test summary line.
Loading…
Cancel
Save