Repository navigation
Closed
Labels
Description
Is there an existing issue for this?
- I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- I have reviewed the documentation https://docs.sentry.io/
- I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/node
SDK Version
9.24.0
Framework Version
Node 22.14.0 LTS
Link to Sentry event
No response
Reproduction Example/SDK Setup
In line 81, a user context is attached to the isolation scope, but (unhandled) exception in line 90 didn't capture this information.
The following function is being called here: https://github.com/zhiyan114/Firey-Bot/blob/275621a29a3f4181857476fd0a002a83301d91e9/src/services/VoiceChatReward.ts#L35C4-L35C71
Steps to Reproduce
For minimal reproduction step, the following code should replicate the exact behavior:
- Use
withIsolationScopewithinsetTimeout,setTimeout, or equivalent.
import { init, captureException, consoleLoggingIntegration, extraErrorDataIntegration, flush, logger, rewriteFramesIntegration, withIsolationScope, withIsolationScope } from "@sentry/node";
function caller(bk: ()=> Promise<void>) {
setTimeout(bk, 1000);
}
process.env["SENTRY_RELEASE"] = execSync(`git -C ${__dirname} rev-parse HEAD`).toString().trim(),
init({
dsn: "someDSN",
maxValueLength: 1000,
tracesSampleRate: 0,
sendDefaultPii: true,
_experiments: {
// Enable logs to be sent to Sentry.
enableLogs: true,
},
integrations: [
extraErrorDataIntegration({
depth: 3
}),
consoleLoggingIntegration({ levels: ['log', 'error', 'warn'] }),
rewriteFramesIntegration({
iteratee(frame) {
const absPath = frame.filename;
if(!absPath) return frame;
frame.filename = `/${path.relative(__dirname, absPath).replace(/\\/g, "/")}`
return frame;
}
}),
],
beforeSend: (a,b) => {
console.log("Sending Error to Sentry...")
return a;
},
beforeBreadcrumb: (a,b) => {
console.log(`Sending Breadcrumb to Sentry: ${a.message}`)
return a;
},
beforeSendTransaction: () => null,
debug: true,
})
caller(async()=>{
await withIsolationScope(async (scope) => {
scope.setUser({
id: 0,
username: "ROOT",
Priv: "SuperUser"
})
console.log("HIII");
throw new Error("TESTING!!!!"); // setUser ctx will not be attached
//captureException(new Error("TESTING!!!!")); // setUser ctx will be attached
})
});Expected Result
Unhandled exception are expected to attach user info context that's wrapped around by the withScope
Actual Result
The given context doesn't show up on the dashboard, the workaround is to force all errors to pass through captureException (which then it wouldn't be unhandled).
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsNo status
Activity
s1gr1d commentedon Jun 3, 2025
Hello, we are aware of this and this is documented in the tests here.
The issue you're experiencing is that thrown errors caught by global error handlers lose scope data from the active scope where the error was originally thrown.
The problem occurs because of how and when the error gets captured:
captureException(error)directly inside thewithScopecallback, it immediately captures the exception using the current active scope at that exact moment. The scope context is available and applied.throw new Error(), the error bubbles up and gets caught by Sentry's global error handlers. By the time these global handlers execute, you're outside the scope context that was active when the error was thrown.You can use
withIsolationScopehere as this does not fork the scopes as frequently aswithScopedoes. Here are the docs for the scopes: https://docs.sentry.io/platforms/javascript/guides/node/enriching-events/scopes/#different-kinds-of-scopeszhiyan114 commentedon Jun 3, 2025
Unfortunately, similar issue also happens when using
withIsolationScope. Using the scope fork outside of the mentioned function will allow the forked scope to attach context normally.[-]withScope failed to capture context for unhandled exception inside setInterval[/-][+]withIsolationScope failed to capture context for unhandled exception inside setInterval[/+]Lms24 commentedon Jun 4, 2025
Hey @zhiyan114!
I tried your reproduction and I can reproduce this but unfortunately, this is expected behaviour. Very similarly to what @s1gr1d explained, also the scope of
withIsolationScopeis only valid within the callback closure. Since the error is bubbling up to the global handler, we're exiting again.So the question left to answer is, why this works in the test Sigrid linked above. This is not very obvious but there's an important difference: In the test, we don't fork the isolation scope (like when using
withIsolationScope) but we actually set the data on the pre-existing isolation scope. This isolation scope is set by ourhttpIntegrationonce the request comes in. This this test is usingexpress, and therefore the errorHandler ofexpressactually captures the error and this still happens within the request life cycle. Since the isolation scope is applied outside of the "express realm", it is still active when our express error handler callscaptureException.I don't know much about your project but I'd recommend trying to do the following for your case:
getIsolationScope().httplifecycle on your server, where we already create a new isolation scope as early as possible automatically. If you don't have this, make sure to callwithIsolationScopeas early as possible yourself and ensure that you callcaptureExceptionyourself.I opened #16479 to demonstrate that calling
withScopein an express app also doesn't retain the data set on that scope on uncaught errors.Side note: We have discussed being a bit smarter about collecting scope data along the outer borders of scope closures so that this data is not lost but actually kept. There are good reasons for doing as well as not doing it. I'll bring this up with the team again but can't promise that we'll make changes.
related: #11133
10 remaining items
Lms24 commentedon Jun 5, 2025
try/catching and calling
captureExceptionwithin yourwithIsolationScopeclosure is one option, yes.If you can tell me more about how your application works (e.g. does it handle requests? is there some kind of request/response life cycle?) maybe there's a better way.
If your application does not handle multiple requests, users or other kinds of parallel tasks, you can also set whatever data you want on the global scope.
Sentry.getGlobalScope().set...works exactly like the isolation and current scopes, just that the data put onto the global scope is added to every event.zhiyan114 commentedon Jun 5, 2025
My application does have request/response cycle, but not in a way a web server would do:
mydea commentedon Jun 10, 2025
You'll probably want to wrap every event emitter callback in a new isolation scope, e.g. (pseudo code incoming) something like this:
That way everything happening inside of that emitter callback should be isolated.
zhiyan114 commentedon Jun 10, 2025
Will give this a try! As for
setInterval, I'll just use try/catch since settingwithIsolationScopedoesn't work within the code when unhandled error throws.