Skip to content

Commit 67e00f5

Browse files
committed
fix(logs): resume the withheld-span fallback from the retained spans' structure budget
1 parent 053c57c commit 67e00f5

2 files changed

Lines changed: 37 additions & 2 deletions

File tree

‎apps/sim/lib/logs/execution/trace-secret-projection.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,33 @@ describe('projectTraceSpansForSecrets whole-trace content budget', () => {
11491149
expect(countSpans(projected)).toBeLessThanOrEqual(100_000)
11501150
})
11511151

1152+
it('keeps the structure a whole-trace fallback would for the span that crossed the budget', async () => {
1153+
const toolCall = (index: number) => ({
1154+
name: `tool-${index}`,
1155+
duration: 0,
1156+
startTime: '2026-07-01T00:00:00.000Z',
1157+
endTime: '2026-07-01T00:00:00.000Z',
1158+
status: 'success' as const,
1159+
})
1160+
const projected = await projectTraceSpansForSecrets(
1161+
[
1162+
heavySpan('span-a', 'first', {
1163+
children: Array.from({ length: 30_000 }, (_, index) => createSpan({ id: `a-${index}` })),
1164+
}),
1165+
createSpan({
1166+
id: 'span-b',
1167+
output: { items: Array.from({ length: 70_000 }, (_, index) => `row-${index}`) },
1168+
toolCalls: Array.from({ length: 50_000 }, (_, index) => toolCall(index)),
1169+
}),
1170+
],
1171+
{ registry: SECRET_REGISTRY, store: STORE, allowLargeValueWrites: false }
1172+
)
1173+
1174+
expect(projected[0].output?.marker).toBe('first')
1175+
expect(projected[1]).not.toHaveProperty('output')
1176+
expect(projected[1].toolCalls).toHaveLength(50_000)
1177+
})
1178+
11521179
it('withholds the whole top-level span whose descendant crossed the budget', async () => {
11531180
const projected = await projectTraceSpansForSecrets(
11541181
[

‎apps/sim/lib/logs/execution/trace-secret-projection.ts‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,6 +1681,7 @@ export async function projectTraceSpansForSecrets(
16811681
assertTraceStructureArrayFits(traceSpans, state)
16821682
const assertContentIsSafe = createSpanContentAssertion(traceSpans, context)
16831683
for (let index = 0; index < traceSpans.length; index += 1) {
1684+
const retainedStructureNodes = state.nodes
16841685
try {
16851686
projected.push(
16861687
await sanitizeTraceSpan(traceSpans[index], context, 0, state, assertContentIsSafe)
@@ -1690,7 +1691,8 @@ export async function projectTraceSpansForSecrets(
16901691
/**
16911692
* Every span already returned was sanitized and then passed the whole-trace check,
16921693
* descendants included, so only the span that crossed the budget and those after it
1693-
* lose their content. The walk stops here, so the budget still bounds it.
1694+
* lose their content. The fallback resumes the structure budget where the retained
1695+
* spans left it, so the trace keeps exactly the structure a whole-trace fallback would.
16941696
*/
16951697
logger.warn(
16961698
'Trace content exceeded the projection budget; withholding the remaining spans',
@@ -1701,7 +1703,13 @@ export async function projectTraceSpansForSecrets(
17011703
failure: describeProjectionFailure(error),
17021704
}
17031705
)
1704-
return [...projected, ...structuralOnlyTraceSpans(traceSpans.slice(index), state)]
1706+
return [
1707+
...projected,
1708+
...structuralOnlyTraceSpans(traceSpans.slice(index), {
1709+
nodes: retainedStructureNodes,
1710+
truncated: false,
1711+
}),
1712+
]
17051713
}
17061714
}
17071715
return projected

0 commit comments

Comments
 (0)