Skip to content

fix: give the Windows smoke suite's SessionStart call its own stdin - #103

Merged
hkwuks merged 5 commits into
mainfrom
fix/windows-smoke-stdin
Oct 28, 2026
Merged

hkwuks merged 5 commits into
mainfrom
fix/windows-smoke-stdin

Conversation

@hkwuks

@hkwuks hkwuks commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Summary

The Windows smoke suite stopped dead whenever its stdin was open and silent. hooks/smoke-test-windows.ps1 ran run-hook.cmd session-start with no stdin of its own, so the hook inherited the runner's — and session-start reads its event with cat. From a terminal that read never reaches EOF, so the suite printed WINDOWS LAUNCHER ARGS PASSED and then hung indefinitely. A red assertion reached a person as an unexplained freeze rather than a message.

The report that produced this task said "a throw after the launcher section hangs the suite". That measures the wrong object. Probing with throw at eight positions (88/95/97/110/121/142/160/197/208), every one before the SessionStart call printed its message and exited 1 in under 14 seconds, and every one after it hung. The boundary is exactly $first = Invoke-SessionStart 'pr'; a throw is never the cause, and the outer try/finally already reports and exits non-zero without any change.

CI could never have caught this — a runner's stdin is empty, so cat sees EOF at once and every windows-latest job has been green regardless. That is also why the assertion has to manufacture the condition rather than wait for CI to supply it.

hooks/smoke-test already carried this lesson for its own session-start call (< /dev/null, comment at :564-566); the PowerShell suite was its unapplied twin. The same inherited stdin also reached one more call — hooks/smoke-test's own == Windows launcher finds Git Bash == section, which runs cmd.exe //d //c ... session-start — and that is fixed the same way.

TaskFlow traceability

  • Task: TaskFlowDocs/2026-10-28-windows-smoke-stdin/
  • Scope: hooks/smoke-test-windows.ps1 (one call, given an empty stdin), hooks/smoke-test (the same fix for its Windows launcher section, plus one end-to-end assertion), CHANGELOG.md. No hook's runtime behaviour changes; hooks/task, and the four hooks' cat contract, are untouched.
  • Base branch: main
  • Target repository: https://github.com/hkwuks/TaskFlow (origin; no fork remotes configured)

Verification

  • The Hooks workflow reports pass for this branch — run to be linked once CI reports
  • git diff --check
  • bash -n hooks/smoke-test
  • Results recorded in the TaskFlow Plan — the CI run is recorded after it reports

Measured locally (WSL + Windows PowerShell 5.1), with an equivalent script rather than the whole suite — see the limitation below:

Check Result
Sub-suite, stdin /dev/null rc=0, 27s, four PASSED banners
Sub-suite, stdin open and silent rc=0, 27s, four PASSED banners
Mutation: line 41 reverted to inherit stdin still silent when the 120s deadline fired; the assertion failed
Restore diff -q byte-identical
Fixed build, same assertion rc=0, 27s

The mutation found a hole in the assertion, not in the code. The first version used sleep 60 as the pipe's writer. With the fix reverted it passed at 79s — the writer exited at 60s, the blocked cat received its EOF, and the suite then completed against a build carrying the defect. The writer must outlive the deadline. At sleep 200 against 120s the reverted build is still silent when the deadline fires. That reasoning is in the comment above the check, because shortening the number gives no warning.

Review boundaries

  • No secrets or opaque remote payloads included
  • No unrelated task or user files changed — the TaskFlowDocs/todo.md diff is one entry, recorded by this task's own intake and promote
  • Remote/base assumptions are stated above
  • Known limitations and follow-ups are documented — see below

Known limitations

  • The full bash hooks/smoke-test could not be run on the author's machine. It stops at == Windows launcher finds Git Bash == because cmd.exe refuses to use a WSL UNC temp directory as its cwd (UNC 路径不受支持), so the section's grep sees only the cmd banner. This predates the change: the same line taken from main produces identical output locally, and adding < /dev/null makes it exit 0 — the section cannot pass under WSL either way. The new assertion was therefore validated with an equivalent script that builds the same stdin, not by a green full suite. The authoritative run is the windows-latest job below.
  • The new assertion runs only where powershell.exe and cygpath are both present, reusing the guard the existing Windows section already has. Ubuntu and macOS skip it, as they skip that whole section.
  • The check costs one more full Windows suite run — about 27s added to the smoke (windows-latest) job. That is the price of a check that can distinguish the fix from the bug; anything cheaper would be asserting the shape of the source line rather than its behaviour.
  • hooks/smoke-test:743 invokes the sub-suite with -ExecutionPolicy Bypass, an existing choice this change does not evaluate.

`hooks/smoke-test-windows.ps1` ran `run-hook.cmd session-start` with no stdin of
its own, so the hook inherited the runner's — and `session-start` reads its event
with `cat`. An open, silent stdin (a terminal, a held pipe) never reaches EOF, so
the suite printed its first banner and then hung indefinitely.

The cause is the inherited stdin, not a throw: probing with `throw` at eight
positions, every one before that call printed and exited 1 in under 14s, while
every one after it hung. The boundary is exactly `Invoke-SessionStart 'pr'`.

CI could never have caught this — a runner's stdin is empty, so `cat` sees EOF at
once and every windows-latest job has been green regardless.

`hooks/smoke-test` now runs the same suite again with a held-open stdin and fails
if it does not finish, which is the condition CI cannot supply. The same inherited
stdin reached one more call, in `hooks/smoke-test`'s own Windows launcher section;
that one is given `< /dev/null` too.
CI caught what the local check could not. `'' |` is not an empty stdin in
PowerShell: the string is encoded and written as a line, and which line depends
on `$OutputEncoding` — 0x0d 0x0a here, a BOM on the runner, where `session-start`
rejected it as invalid JSON and the suite failed (run 40712165629).

`& cmd /c "... < nul"` is zero bytes on every host, and is the shape
`Invoke-Hook` in this same file already uses.
…ns it

The check I added last commit was in hooks/smoke-test, which no CI job executes
on Windows: the workflow gates it on `runner.os != 'Windows'`, and ubuntu and
macos have no powershell.exe or cygpath so the guard skips it. Green CI therefore
said nothing about it — and Windows is the only platform that can reproduce the
defect.

Moved into hooks/smoke-test-windows.ps1, which is the only TaskFlow check a
Windows runner runs. It spawns itself with RedirectStandardInput set and never
written; TASKFLOW_STDIN_SELFCHECK bounds the recursion to one child. Verified by
mutation: reverted to inheriting, the child is killed at the 90s deadline and the
suite fails naming the read; with `< nul` it passes in 51s.
Windows PowerShell 5.1 reads a .ps1 with no BOM using the system ANSI code page,
not UTF-8. An em dash I put in a throw message became three UTF-8 bytes whose
last one swallowed the closing quote, and the whole file stopped parsing --
after Linux and macOS had passed on the same bytes.

The file was already ASCII-only for this reason and builds its Chinese fixtures
from [char] codes; the rule just was not enforced. The suite now reads itself
first and refuses to run if any non-ASCII character is present, naming the line
and the code point.
@hkwuks
hkwuks merged commit 125f052 into main Oct 28, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant