Repository navigation
Conversation
`subprocess.send(fn)` and `process.send(fn)` were accepted. With JSON serialization `send()` returned true and the callback got null, then the channel closed and the messages behind it were lost. node rejects a message whose `typeof` is not string, object, number or boolean. `do_send` tested "object" with `JSValue::is_object()`, which is true for every JSObject, functions included. `JSON.stringify` maps a function to undefined, so an empty frame went on the wire, and the peer closes the channel on a frame it cannot parse. Exclude callable values in that check. A function now throws ERR_INVALID_ARG_TYPE before anything is serialized, in both serialization modes, as in node.
Collaborator
Author
|
Status
|
Contributor
Collaborator
Author
|
Updated 10:27 PM PT - Sep 16th, 2026
✅ @robobun, your commit 4e70a1d45f0f046177214985701a3c8460cc9f54 passed in 🧪 To try this PR locally: bunx bun-pr 43005That installs a local version of the PR into your bun-43005 --bun |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
send(fn)is accepted. In JSON mode the sender writes an empty line and reports success. A Bun receiver closes the channel on that line and loses later messages. A node receiver exits withSyntaxError: Unexpected end of JSON input. node v26.3.0 throwsERR_INVALID_ARG_TYPE.do_send(src/runtime/ipc_host.rs:139). It teststypeof message === "object"withJSValue::is_object(), which is true for a function.JSON.stringify(fn)isundefined, which becomes the empty line.Fix
Proxy). It throwsERR_INVALID_ARG_TYPEbefore anything is serialized, as in node (child_process.js#L780-L786).subprocess.send(),process.send()andBun.spawn'ssend()sharedo_send, in both serialization modes.test/js/node/child_process/child_process_ipc.test.js, all failing on main. Notes has the wire bytes and other suites.Background
do_sendis the native body of every IPCsend(): it validates, serializes and queues the message.json::serializewhenJSON.stringifyreturnsundefined. That also rejects{ toJSON: () => undefined }. node accepts it.Downsides
send()of an object makes one moreisCallablecall, with no allocation or system call.DataCloneError. It now throwsTypeErrorwith codeERR_INVALID_ARG_TYPE.{ toJSON: () => undefined }still writes an empty line. node writesundefinedthere, and the receiver fails the same way.Notes
This replaces #33383, which the stale-PR sweep closed unmerged after
ipc.rsmoved tosrc/runtime/. Since then main started to rethrow a serializer exception (a cyclic message no longer reports success to the callback), so this PR is only the type check.Where the channel breaks. The sender does not close anything. It writes the empty line and continues, and
process.connectedstaystrue. The loss is on the receiving side: Bun's JSON decoder returnsInvalidFormatfor an empty line and closes the socket, and node'sJSON.parse("")throws. #41791 is about how the Bun receiver reports such a line. This PR stops the sender from producing it for a function.Bytes on the wire. A python peer holds the other end of the child's channel (
NODE_CHANNEL_FD, JSON mode) and records every byte. The child sends{n:1}, a function,{n:2},{ toJSON() {} },{n:3}.send(fn)true, callbacknull{"n":1}\n\n{"n":2}\n\n{"n":3}\nERR_INVALID_ARG_TYPE, no callback{"n":1}\n{"n":2}\n\n{"n":3}\nERR_INVALID_ARG_TYPE, no callback{"n":1}\n{"n":2}\nundefined\n{"n":3}\nThe line that remains is the
toJSONobject. node putsundefinedthere and Bun puts nothing. Neither is JSON.Why the sender did not notice.
JSON.stringifyreturnsundefinedas a nullWTF::String.Bun::toStringRefmaps that to theEmptytag, andjson::serialize(src/runtime/ipc.rs:561) only tests forTag::Dead. That test cannot be true for this input. I did not change it here: an error there is the alternative in Background, and it changes thetoJSONcase.Repro
send(fn) threw=ERR_INVALID_ARG_TYPE | follow-up ping delivered=truesend(fn) threw=null | follow-up ping delivered=falsesend(fn) threw=ERR_INVALID_ARG_TYPE | follow-up ping delivered=trueThe full matrix, parent side, after this change (
throw/ callback / what happens next). Every row now matches node:ERR_INVALID_ARG_TYPE, no callback, channel worksERR_INVALID_ARG_TYPE, no callback, channel worksTypeError, no callback, channel workstoJSON()that throws{ toJSON: () => undefined }null, the receiver closes the channelThe last JSON row is node's behavior too: a node child exits with
SyntaxError: "undefined" is not valid JSON. A throw there would validate more strictly than node does.Message text. For a plain function and a class the message is the same as node's (
Received function named,Received function A). For a callableProxyand a bound function the shareddetermineSpecificTypehelper prints a different name than node (Received functionandReceived function b, node printsReceived function targetandReceived function bound b). That helper serves everyERR_INVALID_ARG_TYPE, so the test pins only the code for theProxy.Suites run on the debug build:
child_process_ipc.test.js,child_process_ipc_handle.test.ts,child_process_send_cb.test.js,spawn.ipc.test.ts,spawn.ipc.bun-node.test.ts,spawn.ipc.node-bun.test.ts,cluster.test.ts, and the portedtest-child-process-send-*.jsfiles, which include node's owntest-child-process-send-type-error.js.