You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Modifying error.message does not update error.stack if stream.destroy(error) has been called #51715
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?
import{Writable}from'node:stream'consterror=newError('This failed')conststream=newWritable()stream.destroy(error)stream.on('error',()=>{})error.message=`Additional info: ${error.message}`// This does not print 'Additional info', but shouldconsole.log(error)// Error: This failed// at file:///...// ...
How often does it reproduce? Is there a required condition?
No.
What is the expected behavior? Why is that the expected behavior?
Always stringify error.stack, because the small performance penalty that comes with it is worth the reduced risk of memory leaks. (I’ve confirmed that this also fixes the test with the patch below.)
If error.message is modified later on (e.g. due to prepending some additional information), the change won't be reflected with error.stack. This is unfortunate because error.stack is used by util.inspect(), which is itself used by console.log().
Chrome does not have stream.destroy(), so cannot be impacted by this bug.
error.stack is memoized by V8. However, this is intentional and not a bug. This issue is not reporting error.stack memoization, 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:
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.stack pre-calculation cannot be used, then the internal should never cache the Error since it is unsafe to do so.
Memoizing error.stack in 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 the error in 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.
The V8 implementation might introduce a memory leak per @addaleaxcomment:
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.message does not update error.stack. Modifying error.message in catch block 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. 🤔
@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.
@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 does mean though that the original issue is now a V8 problem, not a Node problem anymore. Originally the error.message change would not be reflected in error.stack in a Node-specific situation, i.e. when stream.destroy(error) has been called, due to Node accessing error.stack early. However, now any error.message change is never reflected in error.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.
Activity
climba03003 commentedon Feb 10, 2024
Shouldn't it be reported to V8 instead?
marco-ippolito commentedon Feb 10, 2024
It seems to be working correctly on Chrome
ehmicky commentedon Feb 10, 2024
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
This workaround was meant for the unit tests, but it is creating user-facing problems.
climba03003 commentedon Feb 11, 2024
@marco-ippolito
I don't think it works in Chrome.
If it works in Chrome, it will works in Node.js.
Chrome 121.0.6167.161

FireFox

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.ehmicky commentedon Feb 11, 2024
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.
climba03003 commentedon Feb 11, 2024
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 the
errorin 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?
ehmicky commentedon Feb 12, 2024
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:
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:
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:
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. 🤔
BridgeAR commentedon 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.
camillobruni commentedon Sep 15, 2025
This was apparently fixed in https://crrev.com/c/5378709 back in 2024-04-04.
BridgeAR commentedon Sep 15, 2025
@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.
ehmicky commentedon Sep 15, 2025
This new V8 behavior was just introduced in Node
24.5.0, which is probably why this issue is getting new activity. For clarity:error.stackwould reflecterror.messageat the timeerror.stackis first accessederror.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.
BridgeAR commentedon Sep 15, 2025
I think we should keep track of this and keep this open. The current behavior is very tricky for users.