Report java failures from the Node.js wrapper instead of returning empty output - #19
Merged
Conversation
…pty output
Stacked on "Run the shaded jar from the Node.js wrapper".
That change fixed which jar the wrapper runs. This one fixes why running the
wrong one looked like success. Three ways the wrapper could not report a
failure:
1. `err` was derived solely from the substring '[ERROR]' appearing in stderr.
Anything that killed the JVM before the compressor could print that marker
came back as err === null with '' as the compressed output - which is how a
NoClassDefFoundError became a silently empty minified file. A non-zero exit
is now an error whatever stderr contains.
2. spawn emits 'error' and never emits 'exit' when java is not on PATH, and
nothing listened for it. The callback was never invoked and the ENOENT was
thrown as an uncaught exception. 'error' on the child and a broken pipe on
its stdin are both handled now, and a single settle() guarantees the caller
is called back exactly once.
3. compress() took the error from fs.readFile and dropped it, passing
undefined to child.stdin.write - ERR_INVALID_ARG_TYPE thrown from inside
the wrapper. It is passed to the caller instead.
The existing contract is preserved: when the compressor itself reports a
problem, err is still the stderr text. Only the cases that previously reported
nothing produce an Error object, and only when stderr is empty.
Verified: the three new tests fail on the unfixed wrapper for their own
reasons - err null despite a non-zero exit, an uncaught 'spawn java ENOENT',
and ERR_INVALID_ARG_TYPE on an unreadable path - and pass after the change.
Replaying the original incident (wrapper pointed at the pre-shade jar) now
returns the NoClassDefFoundError instead of an empty string. nodejs/cli.js is
unaffected.
marevol
force-pushed
the
fix/nodejs-report-java-failures
branch
from
September 5, 2026 04:37
19b561d to
46b6e92
Compare
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.
The defect
Three ways the wrapper could not report a failure.
1. Only the string
[ERROR]counted as an error.Anything that killed the JVM before the compressor could print that marker came
back as
err === nullwith''for the compressed output. That is how theNoClassDefFoundErrorin #18 became a silently empty minified file rather thana build failure. The exit code was right there in the handler and unused.
2.
javamissing fromPATHnever called back at all.spawnemits'error'and never emits'exit', and nothing listened for it,so the ENOENT surfaced as an uncaught exception in whatever code happened to be
running - and the caller's callback was never invoked.
3. A read error was discarded.
A path that exists but cannot be read (a directory, a permissions failure) left
strundefined, andchild.stdin.write(undefined)threwERR_INVALID_ARG_TYPEfrom inside the wrapper.Change
'error'on the child, and a broken pipe on its stdin, are both handled; asingle
settle()guarantees the caller is called back exactly once.The existing contract is preserved: when the compressor itself reports a
problem,
erris still the stderr text. Only the cases that previously reportednothing produce an
Errorobject, and only when stderr is empty.Verification
The three new tests fail on the unfixed wrapper, each for its own reason:
and pass after the change (
# pass 7 # fail 0). Replaying the originalincident - wrapper pointed at the pre-shade jar - now returns the real cause
instead of an empty string:
nodejs/cli.jsis unaffected; the Java suite is unaffected (819 tests, 0 failures).