Skip to content

Commit 4c52da8

Browse files
abhilaksh12claude
authored andcommitted
fix(server-utils): Don't capture AI client errors as unhandled at the instrumentation level
The exported instrumentOpenAiClient, instrumentAnthropicAiClient and instrumentGoogleGenAIClient wrappers captured provider errors with mechanism.handled = false and then rethrew, so the SDK classified the error as an unhandled crash before the application's retry or fallback logic ran. A call that succeeded on retry still produced an unhandled event, and each retry produced another one. Applies the convention established for the channel-based OpenAI integration in #21877 to the manual client instrumentation, which is the only available path on the edge and serverless runtimes. Error span status and the original error identity are unchanged. The anthropic suite asserted this divergence directly, expecting the model-error event only while orchestrion was disabled. Both paths agree now, so that branch and its expectation are gone. Captures are kept where a provider reports an error as data on an otherwise successful call, since the caller never sees those as a thrown error. The AI integration suites no longer mask these events with .ignore('event'), so they fail if the capture returns. Dropping the capture also left the .catch() in createWithResponseWrapper rethrowing into a promise nothing observes, which is not a handler at all: awaiting the two promises in sequence orphans the second whenever the first rejects. They are awaited together now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 05a0fe4 commit 4c52da8

11 files changed

Lines changed: 169 additions & 171 deletions

File tree

dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
GEN_AI_USAGE_TOTAL_TOKENS,
2121
} from '@sentry/conventions/attributes';
2222
import { GEN_AI_REQUEST_STREAM_ATTRIBUTE } from '../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
23-
import { isOrchestrionEnabled } from '../../../utils';
2423
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
2524

2625
describe('Anthropic integration', () => {
@@ -38,25 +37,13 @@ describe('Anthropic integration', () => {
3837
expect(modelsSpan!.status).toBe('ok');
3938
};
4039

41-
const EXPECTED_MODEL_ERROR = {
42-
exception: {
43-
values: [
44-
{
45-
type: 'Error',
46-
value: '404 Model not found',
47-
},
48-
],
49-
},
50-
};
51-
5240
const EXPECTED_STREAM_EVENT_HANDLER_MESSAGE = {
5341
message: 'stream event from user-added event listener captured',
5442
};
5543

5644
createEsmAndCjsTests(__dirname, 'scenario-with-response.mjs', 'instrument.mjs', (createRunner, test) => {
5745
test('preserves .withResponse() and .asResponse() for non-streaming and streaming', async () => {
5846
await createRunner()
59-
.ignore('event')
6047
.expect({
6148
transaction: {
6249
transaction: 'main',
@@ -92,15 +79,7 @@ describe('Anthropic integration', () => {
9279

9380
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
9481
test('creates anthropic related spans with genAI recording disabled', async () => {
95-
const runner = createRunner();
96-
97-
// The orchestrion path only marks the errored span; unlike the OTel path it does not
98-
// capture the handled `error-model` rejection as an event.
99-
if (!isOrchestrionEnabled()) {
100-
runner.expect({ event: EXPECTED_MODEL_ERROR });
101-
}
102-
103-
await runner
82+
await createRunner()
10483
.expect({ transaction: expectModelsSpanOnTransaction })
10584
.expect({
10685
span: container => {
@@ -142,15 +121,7 @@ describe('Anthropic integration', () => {
142121

143122
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
144123
test('creates anthropic related spans with genAI recording enabled', async () => {
145-
const runner = createRunner();
146-
147-
// The orchestrion path only marks the errored span; unlike the OTel path it does not
148-
// capture the handled `error-model` rejection as an event.
149-
if (!isOrchestrionEnabled()) {
150-
runner.expect({ event: EXPECTED_MODEL_ERROR });
151-
}
152-
153-
await runner
124+
await createRunner()
154125
.expect({ transaction: expectModelsSpanOnTransaction })
155126
.expect({
156127
span: container => {
@@ -220,15 +191,7 @@ describe('Anthropic integration', () => {
220191

221192
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-options.mjs', (createRunner, test) => {
222193
test('creates anthropic related spans with custom options', async () => {
223-
const runner = createRunner();
224-
225-
// The orchestrion path only marks the errored span; unlike the OTel path it does not
226-
// capture the handled `error-model` rejection as an event.
227-
if (!isOrchestrionEnabled()) {
228-
runner.expect({ event: EXPECTED_MODEL_ERROR });
229-
}
230-
231-
await runner
194+
await createRunner()
232195
.expect({
233196
transaction: event => {
234197
expect(event.transaction).toBe('main');
@@ -296,7 +259,6 @@ describe('Anthropic integration', () => {
296259
createEsmAndCjsTests(__dirname, 'scenario-stream.mjs', 'instrument.mjs', (createRunner, test) => {
297260
test('streams produce spans with token usage and metadata (PII false)', async () => {
298261
await createRunner()
299-
.ignore('event')
300262
.expect({ transaction: EXPECTED_STREAM_SPANS_PII_FALSE })
301263
.expect({
302264
span: container => {
@@ -350,7 +312,6 @@ describe('Anthropic integration', () => {
350312
createEsmAndCjsTests(__dirname, 'scenario-stream.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
351313
test('streams record response text when PII true', async () => {
352314
await createRunner()
353-
.ignore('event')
354315
.expect({ transaction: EXPECTED_STREAM_SPANS_PII_TRUE })
355316
.expect({
356317
span: container => {
@@ -401,7 +362,6 @@ describe('Anthropic integration', () => {
401362
createEsmAndCjsTests(__dirname, 'scenario-stream-nested-create.mjs', 'instrument.mjs', (createRunner, test) => {
402363
test('traces a create() invoked from a stream event handler (dedup does not over-suppress)', async () => {
403364
await createRunner()
404-
.ignore('event')
405365
.expect({ transaction: { transaction: 'main' } })
406366
.expect({
407367
span: container => {
@@ -432,7 +392,6 @@ describe('Anthropic integration', () => {
432392
const EXPECTED_TOOL_CALLS_JSON =
433393
'[{"type":"tool_use","id":"tool_weather_1","name":"weather","input":{"city":"Paris"}}]';
434394
await createRunner()
435-
.ignore('event')
436395
.expect({
437396
transaction: {},
438397
})
@@ -462,7 +421,6 @@ describe('Anthropic integration', () => {
462421
const EXPECTED_TOOL_CALLS_JSON =
463422
'[{"type":"tool_use","id":"tool_weather_2","name":"weather","input":{"city":"Paris"}}]';
464423
await createRunner()
465-
.ignore('event')
466424
.expect({
467425
transaction: {},
468426
})
@@ -503,6 +461,8 @@ describe('Anthropic integration', () => {
503461
createEsmAndCjsTests(__dirname, 'scenario-stream-errors.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
504462
test('handles streaming errors correctly', async () => {
505463
await createRunner()
464+
// Anthropic surfaces stream errors as events on a resolved stream rather than by rejecting,
465+
// so the instrumentation still reports them; the caller never sees them as a thrown error.
506466
.ignore('event')
507467
.expect({ transaction: EXPECTED_STREAM_ERROR_SPANS })
508468
.expect({
@@ -551,7 +511,6 @@ describe('Anthropic integration', () => {
551511
createEsmAndCjsTests(__dirname, 'scenario-errors.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
552512
test('handles tool errors and model retrieval errors correctly', async () => {
553513
await createRunner()
554-
.ignore('event')
555514
.expect({
556515
transaction: event => {
557516
expect(event.transaction).toBe('main');
@@ -590,7 +549,6 @@ describe('Anthropic integration', () => {
590549
test('extracts system instructions from messages', async () => {
591550
const expectedInstructions = JSON.stringify([{ type: 'text', content: 'You are a helpful assistant' }]);
592551
await createRunner()
593-
.ignore('event')
594552
.expect({
595553
transaction: {
596554
transaction: 'main',
@@ -614,7 +572,6 @@ describe('Anthropic integration', () => {
614572
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-span-streaming.mjs', (createRunner, test) => {
615573
test('creates anthropic related spans with span streaming enabled', async () => {
616574
await createRunner()
617-
.ignore('event')
618575
.expect({
619576
span: container => {
620577
const completionSpan = container.items.find(

dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ describe('Google GenAI integration', () => {
3232
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
3333
test('creates google genai related spans with genAI recording disabled', async () => {
3434
await createRunner()
35-
.ignore('event')
3635
.expect({ transaction: { transaction: 'main' } })
3736
.expect({
3837
span: container => {
@@ -86,7 +85,6 @@ describe('Google GenAI integration', () => {
8685
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
8786
test('creates google genai related spans with genAI recording enabled', async () => {
8887
await createRunner()
89-
.ignore('event')
9088
.expect({ transaction: { transaction: 'main' } })
9189
.expect({
9290
span: container => {
@@ -137,7 +135,6 @@ describe('Google GenAI integration', () => {
137135
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-options.mjs', (createRunner, test) => {
138136
test('creates google genai related spans with custom options', async () => {
139137
await createRunner()
140-
.ignore('event')
141138
.expect({ transaction: { transaction: 'main' } })
142139
.expect({
143140
span: container => {
@@ -174,7 +171,6 @@ describe('Google GenAI integration', () => {
174171
createEsmAndCjsTests(__dirname, 'scenario-tools.mjs', 'instrument-with-options.mjs', (createRunner, test) => {
175172
test('creates google genai related spans with tool calls', async () => {
176173
await createRunner()
177-
.ignore('event')
178174
.expect({ transaction: { transaction: 'main' } })
179175
.expect({
180176
span: container => {
@@ -233,6 +229,7 @@ describe('Google GenAI integration', () => {
233229
createEsmAndCjsTests(__dirname, 'scenario-streaming.mjs', 'instrument.mjs', (createRunner, test) => {
234230
test('creates google genai streaming spans with genAI recording disabled', async () => {
235231
await createRunner()
232+
// Blocked content is reported from within the stream, not thrown to the caller.
236233
.ignore('event')
237234
.expect({ transaction: { transaction: 'main' } })
238235
.expect({
@@ -289,6 +286,7 @@ describe('Google GenAI integration', () => {
289286
createEsmAndCjsTests(__dirname, 'scenario-streaming.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
290287
test('creates google genai streaming spans with genAI recording enabled', async () => {
291288
await createRunner()
289+
// Blocked content is reported from within the stream, not thrown to the caller.
292290
.ignore('event')
293291
.expect({ transaction: { transaction: 'main' } })
294292
.expect({
@@ -348,7 +346,6 @@ describe('Google GenAI integration', () => {
348346
(createRunner, test) => {
349347
test('extracts system instructions from messages', async () => {
350348
await createRunner()
351-
.ignore('event')
352349
.expect({ transaction: { transaction: 'main' } })
353350
.expect({
354351
span: container => {
@@ -372,7 +369,6 @@ describe('Google GenAI integration', () => {
372369
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument.mjs', (createRunner, test) => {
373370
test('creates google genai embeddings spans with genAI recording disabled', async () => {
374371
await createRunner()
375-
.ignore('event')
376372
.expect({ transaction: { transaction: 'main' } })
377373
.expect({
378374
span: container => {
@@ -411,7 +407,6 @@ describe('Google GenAI integration', () => {
411407
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
412408
test('creates google genai embeddings spans with genAI recording enabled', async () => {
413409
await createRunner()
414-
.ignore('event')
415410
.expect({ transaction: { transaction: 'main' } })
416411
.expect({
417412
span: container => {
@@ -458,7 +453,6 @@ describe('Google GenAI integration', () => {
458453
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-span-streaming.mjs', (createRunner, test) => {
459454
test('creates google genai related spans with span streaming enabled', async () => {
460455
await createRunner()
461-
.ignore('event')
462456
.expect({
463457
span: container => {
464458
const generateContentSpan = container.items.find(span => span.name === 'generate_content gemini-1.5-flash');

dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ describe('OpenAI Tool Calls integration', () => {
7878
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
7979
test('creates openai tool calls related spans with genAI recording disabled', async () => {
8080
await createRunner()
81-
.ignore('event')
8281
.expect({ transaction: { transaction: 'main' } })
8382
.expect({
8483
span: container => {
@@ -326,7 +325,6 @@ describe('OpenAI Tool Calls integration', () => {
326325
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
327326
test('creates openai tool calls related spans with genAI recording enabled', async () => {
328327
await createRunner()
329-
.ignore('event')
330328
.expect({ transaction: { transaction: 'main' } })
331329
.expect({
332330
span: container => {

dev-packages/node-integration-tests/suites/tracing/openai/test.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ describe('OpenAI integration', () => {
3333
createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument.mjs', (createRunner, test) => {
3434
test('creates openai related spans with genAI recording disabled', async () => {
3535
await createRunner()
36-
.ignore('event')
3736
.expect({ transaction: { transaction: 'main' } })
3837
.expect({
3938
span: container => {
@@ -333,7 +332,6 @@ describe('OpenAI integration', () => {
333332
createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
334333
test('creates openai related spans with genAI recording enabled', async () => {
335334
await createRunner()
336-
.ignore('event')
337335
.expect({ transaction: { transaction: 'main' } })
338336
.expect({
339337
span: container => {
@@ -681,7 +679,6 @@ describe('OpenAI integration', () => {
681679
createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument-with-options.mjs', (createRunner, test) => {
682680
test('creates openai related spans with custom options', async () => {
683681
await createRunner()
684-
.ignore('event')
685682
.expect({ transaction: { transaction: 'main' } })
686683
.expect({
687684
span: container => {
@@ -726,7 +723,6 @@ describe('OpenAI integration', () => {
726723
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument.mjs', (createRunner, test) => {
727724
test('creates openai related spans with genAI recording disabled', async () => {
728725
await createRunner()
729-
.ignore('event')
730726
.expect({
731727
transaction: {
732728
transaction: 'main',
@@ -859,7 +855,6 @@ describe('OpenAI integration', () => {
859855
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
860856
test('creates openai related spans with genAI recording enabled', async () => {
861857
await createRunner()
862-
.ignore('event')
863858
.expect({
864859
transaction: {
865860
transaction: 'main',
@@ -1089,7 +1084,6 @@ describe('OpenAI integration', () => {
10891084
createEsmAndCjsTests(__dirname, 'scenario-conversation.mjs', 'instrument.mjs', (createRunner, test) => {
10901085
test('captures conversation ID from Conversations API and previous_response_id', async () => {
10911086
await createRunner()
1092-
.ignore('event')
10931087
.expect({
10941088
transaction: {
10951089
transaction: 'conversation-test',
@@ -1209,7 +1203,6 @@ describe('OpenAI integration', () => {
12091203
createEsmAndCjsTests(__dirname, 'scenario-manual-conversation-id.mjs', 'instrument.mjs', (createRunner, test) => {
12101204
test('attaches manual conversation ID set via setConversationId() to all chat spans', async () => {
12111205
await createRunner()
1212-
.ignore('event')
12131206
.expect({
12141207
transaction: {
12151208
transaction: 'chat-with-manual-conversation-id',
@@ -1242,7 +1235,6 @@ describe('OpenAI integration', () => {
12421235
createEsmAndCjsTests(__dirname, 'scenario-separate-scope-1.mjs', 'instrument.mjs', (createRunner, test) => {
12431236
test('isolates conversation IDs across separate scopes - conversation 1', async () => {
12441237
await createRunner()
1245-
.ignore('event')
12461238
.expect({
12471239
transaction: {
12481240
transaction: 'GET /chat/conversation-1',
@@ -1274,7 +1266,6 @@ describe('OpenAI integration', () => {
12741266
createEsmAndCjsTests(__dirname, 'scenario-separate-scope-2.mjs', 'instrument.mjs', (createRunner, test) => {
12751267
test('isolates conversation IDs across separate scopes - conversation 2', async () => {
12761268
await createRunner()
1277-
.ignore('event')
12781269
.expect({
12791270
transaction: {
12801271
transaction: 'GET /chat/conversation-2',
@@ -1310,7 +1301,6 @@ describe('OpenAI integration', () => {
13101301
(createRunner, test) => {
13111302
test('extracts system instructions from messages', async () => {
13121303
await createRunner()
1313-
.ignore('event')
13141304
.expect({
13151305
transaction: {
13161306
transaction: 'main',
@@ -1337,7 +1327,6 @@ describe('OpenAI integration', () => {
13371327
createEsmAndCjsTests(__dirname, 'scenario-with-response.mjs', 'instrument.mjs', (createRunner, test) => {
13381328
test('preserves .withResponse() method and works correctly', async () => {
13391329
await createRunner()
1340-
.ignore('event')
13411330
.expect({
13421331
transaction: {
13431332
transaction: 'main',
@@ -1368,7 +1357,6 @@ describe('OpenAI integration', () => {
13681357
createEsmAndCjsTests(__dirname, 'scenario-chat.mjs', 'instrument-span-streaming.mjs', (createRunner, test) => {
13691358
test('creates openai related spans with span streaming enabled', async () => {
13701359
await createRunner()
1371-
.ignore('event')
13721360
.expect({
13731361
span: container => {
13741362
const chatCompletionSpan = container.items.find(

dev-packages/node-integration-tests/suites/tracing/openai/v6/test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ describe('OpenAI integration (V6)', () => {
3636
(createRunner, test) => {
3737
test('creates openai related spans with genAI recording disabled (v6)', async () => {
3838
await createRunner()
39-
.ignore('event')
4039
.expect({ transaction: { transaction: 'main' } })
4140
.expect({
4241
span: container => {
@@ -346,7 +345,6 @@ describe('OpenAI integration (V6)', () => {
346345
(createRunner, test) => {
347346
test('creates openai related spans with genAI recording enabled (v6)', async () => {
348347
await createRunner()
349-
.ignore('event')
350348
.expect({ transaction: { transaction: 'main' } })
351349
.expect({
352350
span: container => {
@@ -704,7 +702,6 @@ describe('OpenAI integration (V6)', () => {
704702
(createRunner, test) => {
705703
test('creates openai related spans with custom options (v6)', async () => {
706704
await createRunner()
707-
.ignore('event')
708705
.expect({ transaction: { transaction: 'main' } })
709706
.expect({
710707
span: container => {
@@ -767,7 +764,6 @@ describe('OpenAI integration (V6)', () => {
767764
(createRunner, test) => {
768765
test('creates openai related spans with genAI recording disabled (v6)', async () => {
769766
await createRunner()
770-
.ignore('event')
771767
.expect({
772768
transaction: {
773769
transaction: 'main',
@@ -910,7 +906,6 @@ describe('OpenAI integration (V6)', () => {
910906
(createRunner, test) => {
911907
test('creates openai related spans with genAI recording enabled (v6)', async () => {
912908
await createRunner()
913-
.ignore('event')
914909
.expect({
915910
transaction: {
916911
transaction: 'main',

0 commit comments

Comments
 (0)