Skip to content

Commit 74e66bf

Browse files
d-csclaude
andcommitted
fix(webapp): set completedAt for FAILED synthetic run-header
Third surface of the same symmetric pattern (alongside ApiRetrieveRunPresenter and buildSyntheticSpanRun). The run-detail route derives `isCompleted` from `run.completedAt \!== null` and gates SSE live-reloading on it (`route.tsx:459`, `:551`). Leaving the field null for FAILED buffered runs would keep a terminal run live-reloading indefinitely while the badge already says SYSTEM_FAILURE. Falls back to `run.createdAt` for the FAILED case — same approximation used by the sibling helpers. New unit test pins the contract for the NavBar header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5bc96f7 commit 74e66bf

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

apps/webapp/app/v3/mollifier/syntheticRunHeader.server.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,16 @@ export function buildSyntheticRunHeader(args: {
5050
: ("PENDING" as const),
5151
isFinished: isCancelled || isFailed,
5252
startedAt: null,
53-
completedAt: run.cancelledAt ?? null,
53+
// Symmetric with `buildSyntheticSpanRun` and the
54+
// `ApiRetrieveRunPresenter` synth path. The run-detail route
55+
// derives `isCompleted` from `completedAt !== null` and gates SSE
56+
// live-reloading on it (`route.tsx:459`, `:551`); leaving
57+
// `completedAt` null for FAILED would keep a terminal buffered run
58+
// live-reloading forever. PG-resident SYSTEM_FAILURE rows always
59+
// have completedAt set, so fall back to createdAt (the buffer
60+
// entry has no separate failedAt — closest proxy for when the
61+
// terminal state landed).
62+
completedAt: run.cancelledAt ?? (isFailed ? run.createdAt : null),
5463
logsDeletedAt: null,
5564
rootTaskRun: null,
5665
parentTaskRun: null,

apps/webapp/test/mollifierSyntheticRunHeader.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ describe("buildSyntheticRunHeader", () => {
8686
expect(header.completedAt).toEqual(CANCELLED_AT);
8787
});
8888

89+
it("populates completedAt for FAILED runs so the route stops live-reloading and renders as completed", () => {
90+
// The run-detail route derives `isCompleted` from
91+
// `run.completedAt !== null` and gates SSE live-reloading on it
92+
// (`route.tsx:459`, `:551`). Leaving completedAt null for FAILED
93+
// buffered runs would keep a terminal run live-reloading forever
94+
// while the badge already says SYSTEM_FAILURE. Symmetric with
95+
// buildSyntheticSpanRun + ApiRetrieveRunPresenter.
96+
const header = buildSyntheticRunHeader({
97+
run: makeSyntheticRun({ status: "FAILED" }),
98+
environment: ENV,
99+
});
100+
expect(header.status).toBe("SYSTEM_FAILURE");
101+
expect(header.isFinished).toBe(true);
102+
expect(header.completedAt).toEqual(NOW);
103+
});
104+
89105
it("forwards identity and environment fields from the snapshot", () => {
90106
const header = buildSyntheticRunHeader({ run: makeSyntheticRun(), environment: ENV });
91107
expect(header.friendlyId).toBe("run_friendly_1");

0 commit comments

Comments
 (0)