Build1 distinct publisher3 min readPublished
A Windows box running about thirty unattended jobs reported success through three months of failures, because each exit code had to survive a VBScript launcher and a .cmd shim before the scheduler ever saw it.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
In VBScript the parentheses carry meaning. Call a method as a statement and the return value is discarded; parenthesise the argument list and you can take the value out [3]. The launcher therefore waits for the child, receives its exit code and drops it, after which `wscript.exe` reports its own clean exit [1]. The `True` argument is doing exactly what it advertises: it buys synchronisation, not reporting [1]. In a narrow sense the scheduler was telling the truth the whole time.
One layer down, a `.cmd` shim ran `node pipeline.js` and then echoed `%errorlevel%` into the log. A batch file exits with the status of its last command, and `echo` always succeeds, so the shim returned zero whatever node did [6]. The correct non-zero value was sitting in the log file; it never became a process result [6]. Capturing it into `NODE_EXIT` before the echo and ending with `exit /b %NODE_EXIT%` is the whole repair [7].
Count the stages: scheduler, vbs, cmd, python is four, so there are three relays where an exit code has to be handed up [8][1]. Two of them were silently dropping it [1][6]. That is the reason single-layer fixes kept producing green results, and it is also why the 17-launcher number is worth reading twice: seventeen wrappers against roughly thirty jobs means more than half the estate was wired the same way, and exactly one task turned non-zero when the fix landed [5][14][2].
The `pythonw.exe` case is a different failure class. With no console attached, `sys.stdout` is not a usable stream, so a routine encoding guard calling `sys.stdout.reconfigure` raised before the Flask service bound its port, with nowhere to print the traceback [9]. What the operator saw was a closed port after a reboot, which is an invitation to go and read firewall rules [10]. Checking `tasklist` first tells you whether there is a process to blame at all [11].
Then the defaults. A running task was terminated when idle state ended, because the author sat down and moved the mouse, and the recorded result was "terminated", which reads like a crash in the job's own code [12]. `StopOnIdleEnd` is the one setting `New-ScheduledTaskSettingsSet` does not expose, so switching it off needs a second `Set-ScheduledTask` pass after the other four flags go in [13].
The post's stated lesson is to stop checking exit codes and check artifacts [2]. The mechanism above earns that conclusion, but the supplied text asserts the rule without showing the assertion, and an artifact check is only as strong as its predicate: a file left over from yesterday's run satisfies an existence test perfectly well, so freshness and size are the minimum the check has to look at.
For the counts to transfer, your stack has to look like this one: Windows Task Scheduler, VBScript wrappers added to stop console windows flashing, a batch shim in the middle, and about thirty jobs written and maintained by an agent working unattended [1][14]. The codepage item narrows further still, since it only bites where a UTF-8 batch file meets a system codepage that is not UTF-8, CP950 in this case [16]. The structural point survives the narrowing. Every wrapper you add for cosmetic reasons is a place where a zero can be manufactured [1][6].
Ranked by verification strength, evidence, and original report placement.
The author's VBScript launcher called WshShell.Run "cmd /c ...", 0, True as a statement, with 0 to hide the console window and True to wait for completion; used as a statement, Run discards its return value, so wscript.exe exits 0 no matter what the child process did.
The post's stated lesson is: stop checking exit codes, start checking artifacts.
The fix is to call Run as a function and pass the value out with WScript.Quit(exitCode); the parentheses are required when taking a return value.
In a .cmd shim, the last command was an echo of %errorlevel% into the log; because echo always succeeds, the batch file returned zero regardless of what node did, even though the log contained the correct non-zero errorlevel.
The batch fix is to store the value with set NODE_EXIT=%errorlevel% immediately after the node call, then end the file with exit /b %NODE_EXIT%.
The author's failure chain was three layers deep: scheduler to vbs to cmd to python.
Distinct publishers with included, body-backed reporting in this cluster.
1 article · September 5, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
SynkLoader: the Teams help-desk lure now ships with a reverse proxy attached1 distinct publisher
build
A guard that only speaks in exit codes cannot tell you it stopped guarding1 distinct publisher
build
isinstance(amount, (int, float)) is not a number check: NaN walks through a withdrawal guard1 distinct publisher
build
Four test runs, a week's API budget: the seam that gets the model out of CI1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One witness, checkable code
This post actually mixes two very different grades of evidence. The mechanisms come with the code that produces them, and any reader can verify that the statement form of WshShell.Run drops the return value, that a batch file inherits its last command's exit code, and that StopOnIdleEnd is absent from New-ScheduledTaskSettingsSet. The history cannot be verified by anyone: five dead days, 17 repaired launchers, three months of false green and a task that went non-zero "for the first time in its life" rest entirely on the author's reading of logs nobody else has seen.
A single machine, self-reported
The only deployment on show is the author's own: roughly thirty jobs on one Windows box, 17 launchers rewritten in a single sweep, and a scheduler configuration he now applies to every new task. Nobody else appears anywhere in the account, and nothing indicates whether other operators have moved from exit-code checks to artifact checks.
The framing reaches past the box
"Exit code 0 is a lie" is a statement about Windows scripting at large; the supporting material is one laptop whose launchers the author wrote himself and whose system codepage is CP950, which most readers will not share. The individual diagnoses stay narrow and precise, so the stretch is in the title and the round numbers rather than in the technical findings, and the seven-item structure does more work for the headline than for the reader.
Attention is the only payoff
Nothing is being sold: no sponsor, no product benchmark, no tool the author stands to gain from. What remains is the ordinary pull of a developer-platform war story published under a real name, and that pull rewards tidy round figures like three months, seven ways, and thirty jobs more than it rewards precision. It leaves the code samples largely uncontaminated, since a wrong snippet gets corrected by readers within a day.
Mechanisms firm, counts loose
Confidence splits along the same seam as the evidence. Treat the exit-code relay rules, the pythonw stdout behaviour and the scheduler settings as reliable enough to act on tomorrow; treat the timeline and the tallies as one person's recollection, useful as colour and not as a base rate for how often this class of bug bites.