Repository navigation
Modifying error.message does not update error.stack if stream.destroy(error) has been called #51715
Description
Activity
Shouldn't it be reported to V8 instead?
It seems to be working correctly on Chrome
Chrome does not have
stream.destroy(), so cannot be impacted by this bug.error.stackis memoized by V8. However, this is intentional and not a bug. This issue is not reportingerror.stackmemoization, but the improper usage of that memoization.The bug is that Node.js has the following line inside
stream.destroy(error)in order to intentionally use that memoization:node/lib/internal/streams/destroy.js
Lines 33 to 35 in 8a41d9b
if (err) { // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364 err.stack; // eslint-disable-line no-unused-expressions This workaround was meant for the unit tests, but it is creating user-facing problems.
I don't think it works in Chrome.
If it works in Chrome, it will works in Node.js.err = Error('foo') err.stack err.message = `bar ${err.message}` console.log(err)
This issue is not reporting error.stack memoization, but the improper usage of that memoization.
I think it is a bug in V8 because the memorization of stack trace variables leads to the user cannot properly cache or recycle the error when it dispose (it is the cause of memory leak). The problem it tends to stop is not only related to the test case, but also the user environment.
If the trick
err.stackpre-calculation cannot be used, then the internal should never cache theErrorsince it is unsafe to do so.Memoizing
error.stackin V8 is not a bug, it is a performance feature, which has been around for many years. Therefore it is unlikely to be removed by the V8 team.The bug being reported relates to the Node.js stream API, in particular
stream.destroy(), and is not related to Chrome.What can be fixed though is the workaround highlighted in my initial message. It appears that this workaround was intended to fix some automated tests, but it unfortunately creates user-facing issues.
What can be fixed though is the workaround highlighted in my initial message. It appears that this workaround was intended to fix some automated tests, but it unfortunately creates user-facing issues.
If the memory leak exists in test, which means it will also happen in user code.
So, I don't think it only happen in test only.
The proper fix means stream should keep theerrorin reference.If the memory leak did not pop up in first place, there is no need of workaround.
So, it is a bug in V8 which cause memory leak. Isn't it?Yes, that's a good point about this not being only an issue with the automated tests, but a memory leak which could potentially be experienced by users too.
error.stackmemoization with V8 has been around for around 7 years. I have actually written a few libraries to work around this specific issue (set-error-message,set-error-stackandmodern-errors).The V8 implementation might introduce a memory leak per @addaleax comment:
What’s happening is that the error.stack property refers to the full stack frames of its creation. V8 does this to be able to compute error.stack lazily as a string, instead of always formatting it directly even if it isn’t used. Those stack frames in turn can refer to the actual values of variables in that stack frame.
However, if this is the case, it is unclear how many years for this bug to be solved. It is also unclear to me whether the v8 team would agree that this is a bug. As mentioned by @addaleax:
Ask the V8 team for a solution to this. [...] The big downside is that this probably takes some time to implement.
The approach in #34103 has not been to wait for the potential V8 bug to be fixed, but instead of find a workaround right away. That workaround has a problem which (I think) might not have been anticipated based on @addaleax comment:
Always stringify error.stack, because the small performance penalty that comes with it is worth the reduced risk of memory leaks.
Beyond the small performance penalty, this workaround introduces a bigger problem: modifying
error.messagedoes not updateerror.stack. Modifyingerror.messageincatchblock to add some information is a common practice. It seems like this side effect might have been unforeseen, as it is not mentioned in the PR.I am curious whether a different workaround exists that would not have this side effect. 🤔
- addedconfirmed-bugIssues and PRs for confirmed bugs.Issues and PRs for confirmed bugs.v8 engineIssues and PRs related to the V8 dependency.Issues and PRs related to the V8 dependency.
on Sep 12, 2025 @nodejs/v8 would someone be able to check if we could just recalculate the stack part that is related to the error message and error name? I believe the caching is mostly important for the stack frames. The message itself should probably be possible to still change.
This was apparently fixed in https://crrev.com/c/5378709 back in 2024-04-04.
@camillobruni I feel that made it worse, not better. A new message will now never be picked up instead of when it's not yet accessed 😢. The bug report did suggest what I suggested which seems to also align with Firefox and Safari.
This new V8 behavior was just introduced in Node
24.5.0, which is probably why this issue is getting new activity. For clarity:- Before, the message in
error.stackwould reflecterror.messageat the timeerror.stackis first accessed - Now, the message in
error.stackreflectserror.messageat the timenew Error()is constructed. That is, even iferror.messagehas been modified since.
In the meantime, I have created the libraries
set-error-messageandwrap-error-messageto work around this problem.This does mean though that the original issue is now a V8 problem, not a Node problem anymore. Originally the
error.messagechange would not be reflected inerror.stackin a Node-specific situation, i.e. whenstream.destroy(error)has been called, due to Node accessingerror.stackearly. However, now anyerror.messagechange is never reflected inerror.stack, regardless of the situation. From that perspective, it does not make the behavior more consistent, which is what their goal was.Based on this, I am closing this, since this is now a V8 issue.
- Before, the message in
I think we should keep track of this and keep this open. The current behavior is very tricky for users.


Version
v21.1.0
Platform
Linux ether-laptop 6.5.0-15-generic #15-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 9 17:03:36 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
Subsystem
No response
What steps will reproduce the bug?
How often does it reproduce? Is there a required condition?
No.
What is the expected behavior? Why is that the expected behavior?
Printed error should show "Additional info".
What do you see instead?
Printed error does not show "Additional info".
Additional information
This is due to #34103, specifically this:
As implemented in:
node/lib/internal/streams/destroy.js
Line 35 in 8a41d9b
If
error.messageis modified later on (e.g. due to prepending some additional information), the change won't be reflected witherror.stack. This is unfortunate becauseerror.stackis used byutil.inspect(), which is itself used byconsole.log().