11import { millisecondsToNanoseconds , RunAnnotations } from "@trigger.dev/core/v3" ;
22import { prisma , type PrismaClient } from "~/db.server" ;
33import { logger } from "~/services/logger.server" ;
4- import { stripAdminOnlyEventRows } from "~/utils/timelineSpanEvents" ;
54import { getUsername } from "~/utils/username" ;
65import type { SpanSummary } from "~/v3/eventRepository/eventRepository.types" ;
76import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server" ;
@@ -10,8 +9,8 @@ import { env } from "~/env.server";
109import { FEATURE_FLAG } from "~/v3/featureFlags" ;
1110import { makeFlag } from "~/v3/featureFlags.server" ;
1211import { getEventRepositoryForStore } from "~/v3/eventRepository/index.server" ;
13- import { canLiveTail , hasWriteTimes } from "./liveTailGate" ;
14- import { TraceChunkAssembler } from "~/v3/eventRepository/traceChunkAssembler " ;
12+ import { canLiveTail } from "./liveTailGate" ;
13+ import { assembleFirstTraceChunk , type TraceReadScope } from "./traceFirstChunk.server " ;
1514import { applyAncestorOverrides , buildTraceView } from "~/v3/eventRepository/traceViewBuilder" ;
1615import { runStore } from "~/v3/runStore.server" ;
1716import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server" ;
@@ -104,14 +103,12 @@ export class RunPresenter {
104103 select : {
105104 friendlyId : true ,
106105 spanId : true ,
107- createdAt : true ,
108106 } ,
109107 } ,
110108 parentTaskRun : {
111109 select : {
112110 friendlyId : true ,
113111 spanId : true ,
114- createdAt : true ,
115112 } ,
116113 } ,
117114 runtimeEnvironmentId : true ,
@@ -194,8 +191,9 @@ export class RunPresenter {
194191 environment . organizationId
195192 ) ;
196193
194+ // A child view only reads its own lifetime, not the root's.
197195 const traceTimeBounds = {
198- startCreatedAt : run . rootTaskRun ?. createdAt ?? run . createdAt ,
196+ startCreatedAt : run . createdAt ,
199197 endCreatedAt : run . completedAt ?? undefined ,
200198 } ;
201199
@@ -207,8 +205,6 @@ export class RunPresenter {
207205 ? millisecondsToNanoseconds ( run . startedAt . getTime ( ) - triggeredAt . getTime ( ) )
208206 : undefined ;
209207
210- const isRootRunView = ! run . rootTaskRun || run . rootTaskRun . spanId === run . spanId ;
211-
212208 const orgFeatureFlags =
213209 ( authorizedProject . organization ?. featureFlags as Record < string , unknown > ) ?? { } ;
214210
@@ -222,9 +218,7 @@ export class RunPresenter {
222218 } ) ;
223219
224220 const firstChunkPromise = progressivePromise . then ( ( progressiveEnabled ) =>
225- isRootRunView &&
226- progressiveEnabled &&
227- canLiveTail ( run . taskEventStore , env . TRACE_VIEW_EMERGENCY_SPAN_CAP )
221+ progressiveEnabled && canLiveTail ( run . taskEventStore , env . TRACE_VIEW_EMERGENCY_SPAN_CAP )
228222 ? repository . getTraceChunk (
229223 getTaskEventStoreTableForRun ( run ) ,
230224 environment . id ,
@@ -252,98 +246,106 @@ export class RunPresenter {
252246 isAdmin : user ?. admin ?? false ,
253247 } ;
254248
255- if ( firstChunk && hasWriteTimes ( firstChunk . events ) ) {
256- const firstEvents = stripAdminOnlyEventRows ( firstChunk . events , buildOptions . isAdmin ) ;
249+ const traceScope : TraceReadScope = {
250+ storeTable : getTaskEventStoreTableForRun ( run ) ,
251+ environmentId : environment . id ,
252+ traceId : run . traceId ,
253+ startCreatedAt : traceTimeBounds . startCreatedAt ,
254+ endCreatedAt : traceTimeBounds . endCreatedAt ,
255+ } ;
257256
258- const assembler = new TraceChunkAssembler ( ) ;
259- assembler . mergeChunk ( firstEvents ) ;
257+ const first = firstChunk
258+ ? await assembleFirstTraceChunk ( {
259+ repository,
260+ scope : traceScope ,
261+ firstChunk,
262+ anchorSpanId : run . spanId ,
263+ selectedSpanId,
264+ showDebug,
265+ isAdmin : buildOptions . isAdmin ,
266+ } )
267+ : undefined ;
260268
261- if ( assembler . hasSpan ( run . spanId ) ) {
262- let supplementaryFirstEvents : typeof firstChunk . events | undefined ;
269+ if ( first ?. kind === "progressive" ) {
270+ const { spans, overridesBySpanId } = applyAncestorOverrides ( first . assembler . spans ) ;
271+ const view = buildTraceView ( spans , buildOptions ) ;
263272
264- if ( selectedSpanId && selectedSpanId !== run . spanId && ! assembler . hasSpan ( selectedSpanId ) ) {
265- const selectedEvents = await repository . getTraceSpanWithAncestors (
266- getTaskEventStoreTableForRun ( run ) ,
267- environment . id ,
268- run . traceId ,
269- traceTimeBounds . startCreatedAt ,
270- traceTimeBounds . endCreatedAt ,
271- selectedSpanId ,
272- { includeDebugLogs : showDebug }
273- ) ;
274- if ( selectedEvents && selectedEvents . length > 0 ) {
275- const visibleSelected = stripAdminOnlyEventRows ( selectedEvents , buildOptions . isAdmin ) ;
276- assembler . mergeChunk ( visibleSelected , { source : "deeplink" } ) ;
277- supplementaryFirstEvents = visibleSelected ;
278- }
279- }
280-
281- const { spans, overridesBySpanId } = applyAncestorOverrides ( assembler . spans ) ;
282- const view = buildTraceView ( spans , buildOptions ) ;
283-
284- return {
285- run : runData ,
286- trace : {
287- events : view . events ,
288- duration : view . duration ,
289- rootStartedAt : view . rootStartedAt ,
290- rootSpanStatus : view . rootSpanStatus ,
291- startedAt : run . startedAt ,
292- queuedDuration,
293- overridesBySpanId,
294- linkedRunIdBySpanId : view . linkedRunIdBySpanId ,
295- isTruncated : false ,
296- missingAnchor : false ,
297- progressive : {
298- firstEvents,
299- supplementaryFirstEvents,
300- nextCursor : firstChunk . nextCursor ,
301- hasMore : firstChunk . hasMore ,
302- buildOptions,
303- showDebug,
304- maxSpans : repository . maximumTraceViewCount ,
305- liveTailEnabled : true ,
306- firstChunkReadAt,
307- } ,
273+ return {
274+ run : runData ,
275+ trace : {
276+ events : view . events ,
277+ duration : view . duration ,
278+ rootStartedAt : view . rootStartedAt ,
279+ rootSpanStatus : view . rootSpanStatus ,
280+ startedAt : run . startedAt ,
281+ queuedDuration,
282+ overridesBySpanId,
283+ linkedRunIdBySpanId : view . linkedRunIdBySpanId ,
284+ isTruncated : false ,
285+ missingAnchor : false ,
286+ progressive : {
287+ firstEvents : first . firstEvents ,
288+ supplementaryFirstEvents : first . supplementaryFirstEvents ,
289+ nextCursor : first . nextCursor ,
290+ hasMore : first . hasMore ,
291+ buildOptions,
292+ showDebug,
293+ maxSpans : repository . maximumTraceViewCount ,
294+ liveTailEnabled : true ,
295+ firstChunkReadAt,
308296 } ,
309- maximumLiveReloadingSetting : repository . maximumLiveReloadingSetting ,
310- } ;
311- }
297+ } ,
298+ maximumLiveReloadingSetting : repository . maximumLiveReloadingSetting ,
299+ } ;
312300 }
313301
314- // Fast path: full trace summary. Slow path: subtree fetch when the anchor
315- // span fell past the row cap (large traces ordered by start_time ASC).
316- let traceSummary = await repository . getTraceSummary (
317- getTaskEventStoreTableForRun ( run ) ,
318- environment . id ,
319- run . traceId ,
320- traceTimeBounds . startCreatedAt ,
321- traceTimeBounds . endCreatedAt ,
322- { includeDebugLogs : showDebug }
323- ) ;
324-
325- let isTruncated = traceSummary ?. isTruncated ?? false ;
326- const hasAnchorSpan = traceSummary ?. spans . some ( ( span ) => span . id === run . spanId ) ?? false ;
302+ // An empty first chunk, or an anchor the span-id lookup didn't find, means the
303+ // summary and subtree reads would search the same window for nothing.
304+ const anchorMissing = first ?. kind === "fallback" && first . reason === "anchorMissing" ;
305+ const skipSummary = first ?. kind === "empty" || anchorMissing ;
327306
328- if ( traceSummary && ! hasAnchorSpan ) {
329- logger . warn ( "Trace summary missing anchor span, falling back to subtree fetch " , {
307+ if ( anchorMissing ) {
308+ logger . warn ( "Trace first chunk missing anchor span" , {
330309 runId : run . friendlyId ,
331310 spanId : run . spanId ,
332311 traceId : run . traceId ,
333- spanCount : traceSummary . spans . length ,
334312 } ) ;
313+ }
314+
315+ // Fast path: summary rooted at the viewed run. Slow path: subtree fetch when the
316+ // anchor fell past the row cap (large traces ordered by start_time ASC).
317+ let traceSummary = skipSummary
318+ ? undefined
319+ : await repository . getTraceSummary (
320+ getTaskEventStoreTableForRun ( run ) ,
321+ environment . id ,
322+ run . traceId ,
323+ traceTimeBounds . startCreatedAt ,
324+ traceTimeBounds . endCreatedAt ,
325+ { includeDebugLogs : showDebug , anchorSpanId : run . spanId }
326+ ) ;
327+
328+ let isTruncated = traceSummary ?. isTruncated ?? false ;
335329
330+ if ( ! traceSummary && ! skipSummary ) {
336331 const subtreeSummary = await repository . getTraceSubtreeSummary (
337332 getTaskEventStoreTableForRun ( run ) ,
338333 environment . id ,
339334 run . traceId ,
340335 run . spanId ,
341336 traceTimeBounds . startCreatedAt ,
342337 traceTimeBounds . endCreatedAt ,
343- { includeDebugLogs : showDebug }
338+ // The anchor is the viewed run: a root has no ancestors, a child doesn't need them.
339+ { includeDebugLogs : showDebug , includeAncestors : false }
344340 ) ;
345341
346342 if ( subtreeSummary ) {
343+ // Only an anchor past the row cap lands here; an empty trace finds nothing.
344+ logger . warn ( "Trace summary missing anchor span, used subtree fetch" , {
345+ runId : run . friendlyId ,
346+ spanId : run . spanId ,
347+ traceId : run . traceId ,
348+ } ) ;
347349 traceSummary = subtreeSummary ;
348350 isTruncated = subtreeSummary . isTruncated ?? false ;
349351 }
0 commit comments