Skip to content

Commit b4f85a5

Browse files
RulaKhaledclaude
andcommitted
fix(server-utils): Start Flue runs without a recorded admission in clean scopes
A submission admitted by another process, replayed by idempotency key or evicted from the map has no record to restore, so its run kept the context the claim loop inherited from an unrelated request, and lost its `traceparent`. Run it in clean scopes with a new trace instead, through the existing `withCleanScopes` helper, which also gives restored runs a clean current scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent eff7a55 commit b4f85a5

2 files changed

Lines changed: 33 additions & 7 deletions

File tree

‎packages/server-utils/src/ai/flue/index.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import type { Scope, Span } from '@sentry/core';
22
import {
33
continueTrace,
44
getActiveSpan,
5+
getDefaultIsolationScope,
56
getIsolationScope,
67
GLOBAL_OBJ,
78
LRUMap,
89
startNewTrace,
910
startSpan,
1011
withActiveSpan,
11-
withIsolationScope,
1212
} from '@sentry/core';
1313
import {
1414
GEN_AI_AGENT_NAME,
@@ -19,6 +19,7 @@ import {
1919
import type { GenAiOptions } from '../core/utils';
2020
import { getGenAiSpanOp, resolveAIRecordingOptions } from '../core/utils';
2121
import { skipPiAiProviderIntegrations } from '../pi-ai/providers';
22+
import { withCleanScopes } from '../../utils/withCleanScopes';
2223
import { FLUE_INSTRUMENTATION_KEY, FLUE_OPERATION, FLUE_ORIGIN, MAX_TRACKED_FLUE_SPANS } from './constants';
2324
import type { SpanTracker } from './utils';
2425
import {
@@ -45,10 +46,15 @@ interface Admission {
4546
* (https://github.com/withastro/flue/issues/813).
4647
*/
4748
function runInAdmissionContext<T>(admission: Admission | undefined, next: () => T): T {
48-
if (!admission) {
49+
if (isCloudflareWorkers()) {
4950
return next();
5051
}
51-
return withIsolationScope(admission.isolationScope, () =>
52+
if (!admission) {
53+
// Admitted by another process, replayed by idempotency key, or evicted: there is nothing to
54+
// restore, but the loop's context still belongs to an unrelated request.
55+
return withCleanScopes(getDefaultIsolationScope().clone(), () => startNewTrace(next));
56+
}
57+
return withCleanScopes(admission.isolationScope, () =>
5258
admission.span ? withActiveSpan(admission.span, next) : startNewTrace(next),
5359
);
5460
}

‎packages/server-utils/test/ai/lib/tracing/flue.test.ts‎

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,17 +303,37 @@ describe('createFlueInstrumentation', () => {
303303
expect(findSpan('invoke_agent Hello')?.parent_span_id).toBe(alarmSpanId);
304304
});
305305

306-
it('forgets the admission once the submission settled', async () => {
306+
// A submission admitted by another process, replayed by idempotency key, or already settled has
307+
// no record here, but the loop still carries the context of whichever request started it.
308+
it('drops the loop context when nothing was recorded for the submission', async () => {
307309
withActiveSpan(startInactiveSpan({ name: 'POST /agents/hello/b' }), admit);
308310
instrumentation.observe({ type: 'submission_settled', submissionId: 'sub_1' }, {});
309311

310-
let loopSpanId: string | undefined;
312+
let loopTraceId: string | undefined;
311313
await startSpan({ name: 'POST /agents/hello/a' }, span => {
312-
loopSpanId = span.spanContext().spanId;
314+
loopTraceId = span.spanContext().traceId;
313315
return runSubmission();
314316
});
315317

316-
expect(findSpan('invoke_agent Hello')?.parent_span_id).toBe(loopSpanId);
318+
const agent = findSpan('invoke_agent Hello');
319+
expect(agent?.parent_span_id).toBeUndefined();
320+
expect(agent?.trace_id).toMatch(/^[0-9a-f]{32}$/);
321+
expect(agent?.trace_id).not.toBe(loopTraceId);
322+
});
323+
324+
it('continues the replayed traceparent when nothing was recorded for the submission', async () => {
325+
const traceId = 'cccccccccccccccccccccccccccccccc';
326+
await startSpan({ name: 'POST /agents/hello/a' }, () =>
327+
instrumentation.interceptor(SUBMISSION_OP, AGENT_CTX, () =>
328+
instrumentation.interceptor(
329+
AGENT_OP,
330+
{ ...AGENT_CTX, traceCarrier: { traceparent: `00-${traceId}-dddddddddddddddd-01` } },
331+
async () => undefined,
332+
),
333+
),
334+
);
335+
336+
expect(findSpan('invoke_agent Hello')?.trace_id).toBe(traceId);
317337
});
318338
});
319339

0 commit comments

Comments
 (0)