Skip to content

Commit 75c6e2b

Browse files
d-csclaude
andcommitted
fix(webapp): replay dialog falls back to the mollifier buffer
The replay form loader hit `taskRun.findFirst` and threw 404 when the run was buffered, which dumps the user back to the task list. Wire a buffer fallback that synthesises the same loader return shape from the snapshot, including a project-and-environments lookup scoped by the buffer entry's orgId so the env selector renders identically. The replay action itself already supports buffered runs via the ReplayTaskRunService synthetic-run cast — only the form's preflight load was broken. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a1fe5f7 commit 75c6e2b

1 file changed

Lines changed: 69 additions & 1 deletion

File tree

apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
3636
Object.fromEntries(new URL(request.url).searchParams)
3737
);
3838

39-
const run = await $replica.taskRun.findFirst({
39+
let run = await $replica.taskRun.findFirst({
4040
select: {
4141
payload: true,
4242
payloadType: true,
@@ -91,6 +91,74 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
9191
where: { friendlyId: runParam, project: { organization: { members: { some: { userId } } } } },
9292
});
9393

94+
let synthetic:
95+
| (Awaited<ReturnType<typeof findRunByIdWithMollifierFallback>> & { __synth: true })
96+
| undefined;
97+
if (!run) {
98+
// Buffered fallback: read the snapshot and look up the env list via
99+
// the snapshot's organizationId. Without this the Replay dialog
100+
// 404s for runs queued in the mollifier buffer, which dumps the
101+
// user back to the task list.
102+
const buffer = getMollifierBuffer();
103+
const entry = buffer ? await buffer.getEntry(runParam) : null;
104+
if (!entry) throw new Response("Not Found", { status: 404 });
105+
const member = await prisma.orgMember.findFirst({
106+
where: { userId, organizationId: entry.orgId },
107+
select: { id: true },
108+
});
109+
if (!member) throw new Response("Not Found", { status: 404 });
110+
const buffered = await findRunByIdWithMollifierFallback({
111+
runId: runParam,
112+
environmentId: entry.envId,
113+
organizationId: entry.orgId,
114+
});
115+
if (!buffered) throw new Response("Not Found", { status: 404 });
116+
synthetic = Object.assign(buffered, { __synth: true as const });
117+
const orgProject = await $replica.project.findFirst({
118+
where: {
119+
environments: { some: { id: entry.envId } },
120+
},
121+
select: {
122+
slug: true,
123+
environments: {
124+
select: {
125+
id: true,
126+
type: true,
127+
slug: true,
128+
branchName: true,
129+
orgMember: { select: { user: true } },
130+
},
131+
where: {
132+
archivedAt: null,
133+
OR: [
134+
{ type: { in: ["PREVIEW", "STAGING", "PRODUCTION"] } },
135+
{ type: "DEVELOPMENT", orgMember: { userId } },
136+
],
137+
},
138+
},
139+
},
140+
});
141+
if (!orgProject) throw new Response("Not Found", { status: 404 });
142+
run = {
143+
payload: buffered.payload,
144+
payloadType: buffered.payloadType ?? "application/json",
145+
seedMetadata: buffered.seedMetadata ?? null,
146+
seedMetadataType: buffered.seedMetadataType ?? null,
147+
runtimeEnvironmentId: entry.envId,
148+
concurrencyKey: buffered.concurrencyKey ?? null,
149+
maxAttempts: buffered.maxAttempts ?? null,
150+
maxDurationInSeconds: buffered.maxDurationInSeconds ?? null,
151+
machinePreset: buffered.machinePreset ?? null,
152+
workerQueue: buffered.workerQueue ?? null,
153+
ttl: buffered.ttl ?? null,
154+
idempotencyKey: buffered.idempotencyKey ?? null,
155+
runTags: buffered.runTags,
156+
queue: buffered.queue ?? "task/",
157+
taskIdentifier: buffered.taskIdentifier ?? "",
158+
project: orgProject,
159+
} as unknown as typeof run;
160+
}
161+
94162
if (!run) {
95163
throw new Response("Not Found", { status: 404 });
96164
}

0 commit comments

Comments
 (0)