Live CloseHandle breakpoint (WinDbgX conditional bp on the sockListLock
handle value) caught the closer: Tcl thread-exit-handler walk in a dying
Thread-extension worker, tail-calling CloseHandle. The tcludp fossil diff
1.0.12 -> current names it: 1.0.12 Windows UDP_ExitProc ("called at thread
exit") closes the process-global waitForSock and sockListLock events, so the
first udp-loaded thread to die destroys the lock for every other udp-loaded
thread. Recycled handle values turn subsequent INFINITE waits into permanent
blocks (unrecycled values fail fast - why standalone probes stayed green
while punkshell run-2 startup churn wedged 4/4). Upstream already fixed in
1.0.13/trunk (UdpThreadExitProc per-thread + ExitSockets at process exit).
Remedy: upgrade bundled tcludp to 1.0.13+; loose ends recorded (punksys 8.6
udp version check, residual trunk weaknesses as optional upstream tickets).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Instrumented traffic-enabled specimen: wedged workers blocked (not spinning)
in WaitForSingleObject(sockListLock, INFINITE) inside tcludp event-source
procs (disassembly maps both wait sites to the sockListLock static; the
SocketThread released properly and parked on waitForSock). Handle-table data
from the full dump proves both tcludp global event objects were CloseHandle`d
mid-session: the statics still hold handle values now recycled to an
IoCompletion object and a manual-reset event (tcludp only creates auto-reset
events). Waiters starve forever on unsignalable objects. Only ExitSockets
closes these handles; the sole udp-loaded thread that died mid-session (run-1
shellfilter-run worker, terminated in the run transition) is the prime
trigger suspect. Console dependence dissolved - the CONIN$ restart was simply
the only mid-session udp-thread-death scenario. Minimal-repro die-variants
(release / terminate-mimic / full punk env) do not yet reproduce; next
candidates recorded (resultvar-terminate, thread::cancel, live CloseHandle
breakpoint).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Dump pipeline proven (procdump + WinDbgX scripted -z/-c/-logo). Uninstrumented
specimen stacks: workers indistinguishable wedged-vs-idle without heartbeat
timers (all in notifier MsgWait) - round 2 needs the instrumented build to map
wedged tids to dump threads. Structural finds: tcludp runs a global helper
thread (observed parked in WaitForSingleObjectEx inside tcl9udp1012.dll);
udp_tcl.c uses an auto-reset event as a pseudo-lock taken INFINITE in every
udp-owning thread's event-loop setup/check procs; checkProc recvfroms
non-blocking sockets per pass but does not decrement packetNum on the error
path (WSAECONNRESET from ICMP port-unreachable), while setupProc forces
zero block-time poll mode whenever packetNum>0 - a stuck packetNum locks the
owning thread's event loop into permanent poll mode. Blocking-recvfrom theory
ruled out (FIONBIO at open). Round-2 plan recorded (wedged-tid stack, MsgWait
timeout argument, per-thread CPU to discriminate spin vs block).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
TEMP_REFERENCE checkouts confirmed current (Tcl core 2026-07-07, tcludp
2026-07-05). core-9-0-branch carries no tclWinConsole.c fix since 9.0.2, so
the wedge - if in the console driver - is likely still present upstream.
Current driver architecture recorded (process-global gConsoleLock, console
handle list shared across all Tcl threads, per-handle SRWLOCKs, reader/writer
threads with CONDITION_VARIABLE wakeups + Tcl_ThreadAlert fan-out) as the
credible wedge surface. Worker-side hypothesis sharpened: std-channel init on
a worker's first channel creation (udp_open / log-file open) attaches it to
the shared console machinery when the process stdin handle is the console -
fits which workers wedge vs survive, though probes have not yet reproduced.
Probe 'adopt' variant added (main thread closes stdin, CONIN$ adopted into
the stdin slot, SetStdHandle, active reader) - clean, ruling out the last
easy stdin-arrangement candidate. Approach reordered: native thread stacks
of a wedged in-context specimen promoted to step 1 (no debugger installed
yet - tool options noted).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Index entry recorded active per user. The detail file carries the full
investigation record: diagnosis chain of the piped-stdin exit/quit hang,
evidence and scoring caveats, ruled-out repro ingredients, remaining
candidates, and inline tooling listings (standalone probe script and the
hidden-console batch scorer with its 2026-07-08 baseline: punk902z run-2
syslog workers wedged 4/4, punksys 8.6 alive 4/4). Upstream reporting is the
user's manual step, gated on re-verifying against a Tcl 9 built from current
core sources.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com