Skip to content

Kiln's Node range admits Windows releases where an RPC launch can crash at exit #218

Closed

Description

@don-milsey-miller

Summary

Kiln's package.json declares "node": ">=22.19.0". That range admits Node releases on which a Windows --rpc launch can end with a crash status even though its work completed. The crash is a Node defect, not Kiln's, and is fixed in Node 24.20.0. Kiln neither documents nor checks the affected versions.

Follow-up to #206, which attributed the crash and was closed with no Kiln change.

Evidence

From #206, measured on Windows 11 with Pi 0.87.1:

Node Result
22.23.3 no crash
24.18.0 crashes
24.19.0 crashes
24.20.0 no crash
24.21.0 no crash

On an affected version, Pi's RPC shutdown calls process.exit() and Node can abort with Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c and exit code 0xC0000409. Since #200 the supervisor reports that code correctly, so the launch ends with a crash status.

Upstream: nodejs/node#56645, fixed by nodejs/node#61999 in 24.20.0. earendil-works/pi#9507 is the same crash seen from Pi.

Not measured: Node 23.x, Node 24 releases before 24.18.0, and Node 25 and later. The Node issue names 23.x and later as affected. Which 25.x or later release first carries the fix has not been checked. Only Windows is affected.

Scope

Document or enforce the Node versions on which Windows RPC launches are safe. Out of scope: any delay, retry or suppression around Pi's exit, and any change to how the job host or supervisor reports the exit code.

Questions to decide

Suggested acceptance criteria

  • The affected range is established for every Node major Kiln supports, including the first fixed release after 24.x.
  • The decision above is implemented: documentation, a launch warning, or a launch refusal with a stable code.
  • A check, if added, reads only the Node version and platform, and has a test for an affected and an unaffected version.
  • Linux and macOS launches are unchanged.

Activity

  1. don-milsey-miller commented on Oct 13, 2026

    @don-milsey-miller
    OwnerAuthor

    Investigation outcome

    Measured on Windows 11 Pro 10.0.26300 from main at bc7d8ca, with Pi 0.87.1 started directly: no Kiln extension, supervisor or job host, and the loopback provider fixture. Every Node build is the official win-x64/node.exe, checked against its release's SHASUMS256.txt.

    Each run is two launches on one session id. The first creates the session and the second resumes it with a 256 KB answer, which makes Pi compact its context. That resumed launch is the workload #206 found to crash.

    1. --mode rpc across every Node major the engines range admits

    Ten runs per version. The client closes standard input after agent_settled.

    Node V8 Resumed launch crashed
    22.19.0 12.4 0 of 10
    22.23.3 12.4 0 of 10
    23.0.0 12.9 10 of 10
    23.11.1 12.9 10 of 10
    24.0.0 13.6 10 of 10
    24.18.1 13.6 10 of 10
    24.19.0 13.6 9 of 10
    24.20.0 13.6 0 of 10
    24.21.0 13.6 0 of 10
    25.0.0 14.1 10 of 10
    25.9.0 14.1 10 of 10
    26.0.0 14.6 10 of 10
    26.5.0 14.6 9 of 10
    26.6.0 14.6 10 of 10
    26.7.0 14.6 0 of 10
    26.11.1 14.6 0 of 10

    Every crash was exit code 0xC0000409 with Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c. The first launch of each run, which creates the session, crashed 0 of 160 times.

    2. The other launch modes

    The same workload, on affected versions 23.0.0, 23.11.1, 24.0.0, 24.19.0, 25.9.0 and 26.6.0, and on 22.23.3, 24.20.0 and 26.7.0 as controls. Counts are resumed launches.

    Pi mode Kiln launch that uses it Affected versions Controls
    Interactive, in a pseudo terminal, quit with /quit terminal launch 0 of 18 0 of 9
    Interactive, shut down by an extension on agent_end recovery 0 of 36 0 of 18
    Print (-p) piped launch 0 of 48 0 of 24
    JSON print (--mode json -p) delegated children 0 of 48 0 of 24

    Only RPC mode crashed. Reading Pi's code gives a reason that matches: RPC mode calls process.exit() straight after disposing the runtime, interactive mode first waits up to one second for terminal input to drain, and print modes return from main and let Node exit on its own.

    3. Affected range, from Node's history and from the boundaries above

    • Introduced by the V8 12.8 update (nodejs/node#54077), which the bisect in nodejs/node#56645 points to. It first shipped in 23.0.0. Node 22 stays on V8 12.4.
    • Fixed by nodejs/node#61999, merged 2026-07-24. The changelogs list it in 24.20.0 and 26.7.0 only.
    • The 23.x and 25.x lines ended before the fix merged, so neither has a safe release.

    Unsafe: all of 23.x, 24.0 through 24.19, all of 25.x, and 26.0 through 26.6. Safe: 22.x, 24.20.0 and later 24.x, 26.7.0 and later 26.x, and 27 onward.

    4. Determinism

    Not strictly deterministic. Two affected versions crashed 9 of 10, and the crash depends on the workload: a new session never crashed, and in #206 the same Pi with short answers crashed 0 of 160 launches on 24.18.0. In Kiln's own test/rpc-start.test.mjs on 24.18.0 the rate was 1 of 6 on main and 2 of 5 on a feature branch.

    5. Where Kiln can check

    Pi runs under process.execPath, so the launcher's Node is Pi's. main in bin/start-kiln.mjs knows the mode once arguments are parsed. A check fits immediately after the output mode is set, before the project root is resolved, before the launch checks, and before any process starts.

    6. Decision

    Refuse start-kiln --rpc on Windows under an unsafe Node, before anything starts, with exit status 2 and the stable code node-version-unsafe-for-rpc. There is no override. Terminal and piped launches and every non-Windows launch are unchanged. engines.node stays >=22.19.0: Node 22 is safe, the defect is Windows-only and one launch mode is affected, so there is no platform-independent unsafe range. The README and docs/running-the-shell.md will state the range.

    Not measured

    • Linux and macOS. The Node issue reports Windows only.
    • Kiln's own Node processes, such as the supervisor and launcher, which also call process.exit().
    • Interactive mode was sampled at 3 runs per version for an operator quit and 6 for a programmatic shutdown.
  2. added a commit that references this issue on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions