stream: allow null as second arg in Transform callback#62803
Conversation
|
Review requested:
|
|
Looking at the history, The fix changes to
In practice, That said, I'm uncertain whether this is semver-patch (bug fix aligning with docs) or semver-minor (observable behavior change). Would appreciate guidance on the right label and whether a CHANGELOG note is needed. |
Codecov Reportβ
All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #62803 +/- ##
==========================================
- Coverage 89.70% 89.68% -0.02%
==========================================
Files 706 706
Lines 218288 218222 -66
Branches 41782 41766 -16
==========================================
- Hits 195806 195714 -92
- Misses 14394 14412 +18
- Partials 8088 8096 +8
π New features to boost your workflow:
|
mcollina
left a comment
There was a problem hiding this comment.
This should have a pass of citgm before landing.
I expect a significant breakage in the ecosystem.
Thank you for your review. |
citgm run β resultsTL;DR: Full citgm-all + extended stream subset + pattern audit show zero ecosystem regressions. Environment
Full citgm-all (72 modules)
Pre-existing failures (identical on both binaries)
All fail with the same error on both binaries (yarn resolution, native addon build on Apple clang 21, environment/flake). Spot-checked Extended stream subset (17 modules not in citgm lookup)Ran against
Pattern auditGrepped
Hits outside Transform context: webpack (7), undici (2), mongoose (8) β all in unrelated async callbacks (resolver cache, dispatcher, cursor), not Hits inside Transform context: 0. Semantic differentialConfirmed the change's behavior is observable with a focused test: const t = new Transform({
transform(chunk, enc, cb) {
if (chunk.toString() === 'stop') cb(null, null);
else cb(null, chunk);
},
});
t.on('data', (d) => received.push(d));
t.write('a'); t.write('stop'); t.write('b'); t.end();
So the change is a real semantic change β but we found no library in the tested universe that relies on it. Applications using Conclusion & SemVer recommendation
Given the pattern is not observed in the ecosystem but is a runtime-visible change, I'd suggest Alternatives considered
Raw artifactsAvailable on request β stdout logs, per-module TAP, and pattern-hits file can be uploaded to a Gist if useful. Notes
|
callback(null, null) in a Transform._transform method should be equivalent to calling this.push(null) followed by callback(), ending the readable side of the stream. Previously val != null blocked the push because null == null is true in loose equality, so push(null) was never called and the stream never emitted 'end'. Change the guard from `val != null` to `val !== undefined` so that null passes through to this.push(), which sets state.ended and eventually emits 'end', matching documented behavior. Fixes: nodejs#62769
4aefb07 to
3040233
Compare
@mcollina I ran citgm locally on macOS (arm64) and have attached the results above. Please take a look whenever you get a chance β no rush at all. Hope you have a great day! |
|
lowered to semver-minor with a "baking for lts" label. |
|
@lpinca @ronag I'd really appreciate it if you could take another look and re-approve whenever you have a moment. Have a great day! |
|
Fixes: #62769 |
Commit Queue failed- Loading data for nodejs/node/pull/62803 β Done loading data for nodejs/node/pull/62803 ----------------------------------- PR info ------------------------------------ Title stream: allow null as second arg in Transform callback (#62803) Author eungi <deveungi@gmail.com> (@galaxy4276) Branch galaxy4276:stream/fix-transform-callback-null -> nodejs:main Labels stream, semver-minor, baking-for-lts, needs-ci Commits 1 - stream: allow null as second arg in Transform callback Committers 1 - galaxy4276 <deveungi@gmail.com> PR-URL: https://github.com/nodejs/node/pull/62803 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> ------------------------------ Generated metadata ------------------------------ PR-URL: https://github.com/nodejs/node/pull/62803 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> -------------------------------------------------------------------------------- βΉ This PR was created on Sat, 18 Apr 2026 13:12:12 GMT β Approvals: 3 β - Robert Nagy (@ronag) (TSC): https://github.com/nodejs/node/pull/62803#pullrequestreview-4134963199 β - Luigi Pinca (@lpinca): https://github.com/nodejs/node/pull/62803#pullrequestreview-4134589432 β - Matteo Collina (@mcollina) (TSC): https://github.com/nodejs/node/pull/62803#pullrequestreview-4138192639 β Last GitHub CI successful βΉ Last Full PR CI on 2026-04-20T08:33:03Z: https://ci.nodejs.org/job/node-test-pull-request/72794/ - Querying data for job/node-test-pull-request/72794/ β Last Jenkins CI successful -------------------------------------------------------------------------------- β No git cherry-pick in progress β No git am in progress β No git rebase in progress -------------------------------------------------------------------------------- - Bringing origin/main up to date... From https://github.com/nodejs/node * branch main -> FETCH_HEAD β origin/main is now up-to-date - Downloading patch for 62803 From https://github.com/nodejs/node * branch refs/pull/62803/merge -> FETCH_HEAD β Fetched commits as 040c51c98f4f..3040233e7cef -------------------------------------------------------------------------------- [main a183368f0a] stream: allow null as second arg in Transform callback Author: galaxy4276 <deveungi@gmail.com> Date: Sun Apr 19 23:13:42 2026 +0900 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-stream-transform-callback-null.js β Patches applied -------------------------------------------------------------------------------- --------------------------------- New Message ---------------------------------- stream: allow null as second arg in Transform callbackhttps://github.com/nodejs/node/actions/runs/26708502888 |
What
The docs say passing a value as the second argument to the transform callback is equivalent to calling
transform.push(value). That impliescallback(null, null)should pushnullβ ending the readable side β just likethis.push(null); callback()does. It doesn't.Bug
The
'end'event never fires. The stream hangs.Root cause β
lib/internal/streams/transform.js:val != nulluses loose equality, so bothnullandundefinedfail the check.this.push(null)is never called,state.endedstaysfalse, and'end'is never emitted.Fix
Now
callback(null, null)reachesthis.push(null), which setsstate.ended = trueand eventually emits'end'once the buffer drains β matching what the docs describe.Behavior after fix
Potential concern
This is technically a behavior change. Code that relied on
callback(null, null)not ending the stream should usecallback()(no second argument) instead. I think this is the right call given the docs, but open to pushback β especially on whether this warrants a semver-minor label or a deprecation note before changing.