Skip to content

Commit 9b63809

Browse files
committed
fix(webapp): answer not-found for unroutable ids on the remaining run routes
The API route builders cover the routes built on them, but routes that handle their own errors never reach that mapping. Four bare API routes answered 500 for an id naming a database the deployment has no store for, and ten dashboard and resource routes threw straight through to an error page. Each now takes the not-found path it already has for a run that does not exist: the API routes return 404, the dashboard routes redirect or 404 as they already did. A shared helper keeps that uniform, and every one logs first, so a shard key dropped from a config meant to be append-only still alarms rather than reading as an absent run.
1 parent ff518b5 commit 9b63809

15 files changed

Lines changed: 231 additions & 131 deletions

apps/webapp/app/routes/@.runs.$runParam.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { redirectWithErrorMessage } from "~/models/message.server";
88
import { requireUser } from "~/services/session.server";
99
import { impersonate, rootPath, v3RunPath, v3RunSpanPath } from "~/utils/pathBuilder";
1010
import { findBufferedRunRedirectInfo } from "~/v3/mollifier/syntheticRedirectInfo.server";
11+
import { undefinedOnUnroutableId } from "~/v3/runOpsMigration/unroutableRead.server";
1112

1213
const ParamsSchema = z.object({
1314
runParam: z.string(),
@@ -33,17 +34,20 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
3334
);
3435
}
3536

36-
const run = await runStore.findRun(
37-
{
38-
friendlyId: runParam,
39-
},
40-
{
41-
select: {
42-
spanId: true,
43-
runtimeEnvironmentId: true,
37+
const run = await undefinedOnUnroutableId(
38+
runStore.findRun(
39+
{
40+
friendlyId: runParam,
41+
},
42+
{
43+
select: {
44+
spanId: true,
45+
runtimeEnvironmentId: true,
46+
},
4447
},
45-
},
46-
prisma
48+
prisma
49+
),
50+
{ runParam: params.runParam ?? params.runId }
4751
);
4852

4953
if (!run) {

apps/webapp/app/routes/api.v1.batches.$batchParam.results.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { runOpsLegacyReplica, runOpsNewReplica, runOpsSplitReadEnabled } from "~
55
import { ApiBatchResultsPresenter } from "~/presenters/v3/ApiBatchResultsPresenter.server";
66
import { authenticateApiRequest } from "~/services/apiAuth.server";
77
import { logger } from "~/services/logger.server";
8+
import { unroutableIdResponse } from "~/services/routeBuilders/unroutableId.server";
89

910
const ParamsSchema = z.object({
1011
/* This is the batch friendly ID */
@@ -42,6 +43,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
4243

4344
return json(result);
4445
} catch (error) {
46+
const unroutable = unroutableIdResponse(error);
47+
if (unroutable) {
48+
logger.warn("Unroutable batch id on batch results", {
49+
error: error instanceof Error ? error.message : error,
50+
});
51+
return unroutable;
52+
}
53+
4554
logger.error("Failed to load batch results", { error });
4655
return json({ error: "Something went wrong, please try again." }, { status: 500 });
4756
}

apps/webapp/app/routes/api.v1.runs.$runId.tags.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { logger } from "~/services/logger.server";
1010
import { publishChangeRecord } from "~/services/realtime/runChangeNotifierInstance.server";
1111
import { mutateWithFallback } from "~/v3/mollifier/mutateWithFallback.server";
1212
import { runStore } from "~/v3/runStore.server";
13+
import { unroutableIdResponse } from "~/services/routeBuilders/unroutableId.server";
1314

1415
// Pull the existing tags out of a buffer entry's serialised payload so
1516
// the buffer-path response can dedup against them, matching the
@@ -137,6 +138,14 @@ export async function action({ request, params }: ActionFunctionArgs) {
137138
}
138139
return outcome.response;
139140
} catch (error) {
141+
const unroutable = unroutableIdResponse(error);
142+
if (unroutable) {
143+
logger.warn("Unroutable run id on run tags", {
144+
error: error instanceof Error ? error.message : error,
145+
});
146+
return unroutable;
147+
}
148+
140149
logger.error("Failed to add run tags", { error });
141150
return json({ error: "Something went wrong, please try again." }, { status: 500 });
142151
}

apps/webapp/app/routes/api.v1.runs.$runParam.reschedule.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { RescheduleTaskRunService } from "~/v3/services/rescheduleTaskRun.server
1212
import { mutateWithFallback } from "~/v3/mollifier/mutateWithFallback.server";
1313
import { getMollifierBuffer } from "~/v3/mollifier/mollifierBuffer.server";
1414
import { parseDelay } from "~/utils/delays";
15+
import { unroutableIdResponse } from "~/services/routeBuilders/unroutableId.server";
1516

1617
const ParamsSchema = z.object({
1718
runParam: z.string(),
@@ -156,6 +157,14 @@ export async function action({ request, params }: ActionFunctionArgs) {
156157
if (error instanceof ServiceValidationError) {
157158
return json({ error: error.message }, { status: 400 });
158159
}
160+
const unroutable = unroutableIdResponse(error);
161+
if (unroutable) {
162+
logger.warn("Unroutable run id on reschedule", {
163+
error: error instanceof Error ? error.message : error,
164+
});
165+
return unroutable;
166+
}
167+
159168
logger.error("Failed to reschedule run", { error });
160169
return json({ error: "Something went wrong, please try again." }, { status: 500 });
161170
}

apps/webapp/app/routes/api.v1.runs.$runParam.result.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ApiRunResultPresenter } from "~/presenters/v3/ApiRunResultPresenter.ser
55
import { runOpsLegacyReplica, runOpsNewReplica, runOpsSplitReadEnabled } from "~/db.server";
66
import { authenticateApiRequest } from "~/services/apiAuth.server";
77
import { logger } from "~/services/logger.server";
8+
import { unroutableIdResponse } from "~/services/routeBuilders/unroutableId.server";
89

910
const ParamsSchema = z.object({
1011
/* This is the run friendly ID */
@@ -41,6 +42,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
4142

4243
return json(result);
4344
} catch (error) {
45+
const unroutable = unroutableIdResponse(error);
46+
if (unroutable) {
47+
logger.warn("Unroutable run id on run result", {
48+
error: error instanceof Error ? error.message : error,
49+
});
50+
return unroutable;
51+
}
52+
4453
logger.error("Failed to load run result", { error });
4554
return json({ error: "Something went wrong, please try again." }, { status: 500 });
4655
}

apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.runs.$runParam.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { requireUserId } from "~/services/session.server";
66
import { ProjectParamSchema, v3RunPath } from "~/utils/pathBuilder";
77
import { runStore } from "~/v3/runStore.server";
88
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
9+
import { undefinedOnUnroutableId } from "~/v3/runOpsMigration/unroutableRead.server";
910

1011
const ParamSchema = ProjectParamSchema.extend({
1112
runParam: z.string(),
@@ -15,16 +16,19 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
1516
const userId = await requireUserId(request);
1617
const { organizationSlug, projectParam, runParam } = ParamSchema.parse(params);
1718

18-
const run = await runStore.findRun(
19-
{
20-
friendlyId: runParam,
21-
},
22-
{
23-
select: {
24-
projectId: true,
25-
runtimeEnvironmentId: true,
19+
const run = await undefinedOnUnroutableId(
20+
runStore.findRun(
21+
{
22+
friendlyId: runParam,
2623
},
27-
}
24+
{
25+
select: {
26+
projectId: true,
27+
runtimeEnvironmentId: true,
28+
},
29+
}
30+
),
31+
{ runParam: params.runParam ?? params.runId }
2832
);
2933

3034
if (!run) {

apps/webapp/app/routes/projects.v3.$projectRef.runs.$runParam.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { requireUserId } from "~/services/session.server";
55
import { v3RunSpanPath } from "~/utils/pathBuilder";
66
import { runStore } from "~/v3/runStore.server";
77
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
8+
import { undefinedOnUnroutableId } from "~/v3/runOpsMigration/unroutableRead.server";
89

910
const ParamsSchema = z.object({
1011
projectRef: z.string(),
@@ -36,18 +37,21 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
3637
return new Response("Not found", { status: 404 });
3738
}
3839

39-
const run = await runStore.findRun(
40-
{
41-
friendlyId: validatedParams.runParam,
42-
},
43-
{
44-
select: {
45-
friendlyId: true,
46-
spanId: true,
47-
runtimeEnvironmentId: true,
40+
const run = await undefinedOnUnroutableId(
41+
runStore.findRun(
42+
{
43+
friendlyId: validatedParams.runParam,
4844
},
49-
},
50-
prisma
45+
{
46+
select: {
47+
friendlyId: true,
48+
spanId: true,
49+
runtimeEnvironmentId: true,
50+
},
51+
},
52+
prisma
53+
),
54+
{ runParam: params.runParam ?? params.runId }
5155
);
5256

5357
if (!run) {

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.sessions.$sessionId.$io.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { getRealtimeStreamInstance } from "~/services/realtime/v1StreamsGlobal.server";
1414
import { requireUserId } from "~/services/session.server";
1515
import { EnvironmentParamSchema } from "~/utils/pathBuilder";
16+
import { undefinedOnUnroutableId } from "~/v3/runOpsMigration/unroutableRead.server";
1617

1718
const ParamsSchema = z.object({
1819
runParam: z.string(),
@@ -61,8 +62,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
6162
// Replica lag can null out a live run; a spurious 404 breaks the dashboard Agent tab subscription
6263
// (useRealtimeStream surfaces the error and does not auto-retry). Re-read the primary on a miss.
6364
const run =
64-
(await runStore.findRun(runWhere, runArgs, $replica)) ??
65-
(await runStore.findRunOnPrimary(runWhere, runArgs));
65+
(await undefinedOnUnroutableId(runStore.findRun(runWhere, runArgs, $replica), {
66+
runParam: params.runParam,
67+
})) ??
68+
(await undefinedOnUnroutableId(runStore.findRunOnPrimary(runWhere, runArgs), {
69+
runParam: params.runParam,
70+
}));
6671

6772
if (!run) {
6873
return new Response("Run not found", { status: 404 });

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.streams.$runId.$streamId.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
88
import { requireUserId } from "~/services/session.server";
99
import { EnvironmentParamSchema } from "~/utils/pathBuilder";
1010
import { runStore } from "~/v3/runStore.server";
11+
import { undefinedOnUnroutableId } from "~/v3/runOpsMigration/unroutableRead.server";
1112

1213
const ParamsSchema = z.object({
1314
runParam: z.string(),
@@ -60,8 +61,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
6061
// Replica lag can null out a live run; a spurious 404 breaks the dashboard Agent tab subscription
6162
// (useRealtimeStream surfaces the error and does not auto-retry). Re-read the primary on a miss.
6263
const run =
63-
(await runStore.findRun(runWhere, runArgs, $replica)) ??
64-
(await runStore.findRunOnPrimary(runWhere, runArgs));
64+
(await undefinedOnUnroutableId(runStore.findRun(runWhere, runArgs, $replica), {
65+
runParam: params.runParam,
66+
})) ??
67+
(await undefinedOnUnroutableId(runStore.findRunOnPrimary(runWhere, runArgs), {
68+
runParam: params.runParam,
69+
}));
6570

6671
if (!run) {
6772
return new Response("Run not found", { status: 404 });

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.realtime.v1.streams.$runId.input.$streamId.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
88
import { requireUserId } from "~/services/session.server";
99
import { EnvironmentParamSchema } from "~/utils/pathBuilder";
1010
import { runStore } from "~/v3/runStore.server";
11+
import { undefinedOnUnroutableId } from "~/v3/runOpsMigration/unroutableRead.server";
1112

1213
const ParamsSchema = z.object({
1314
runParam: z.string(),
@@ -62,8 +63,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
6263
// Replica lag can null out a live run; a spurious 404 breaks the dashboard Agent tab input-stream
6364
// subscription (useRealtimeStream surfaces the error, no auto-retry). Re-read the primary on a miss.
6465
const run =
65-
(await runStore.findRun(runWhere, runArgs, $replica)) ??
66-
(await runStore.findRunOnPrimary(runWhere, runArgs));
66+
(await undefinedOnUnroutableId(runStore.findRun(runWhere, runArgs, $replica), {
67+
runParam: params.runParam,
68+
})) ??
69+
(await undefinedOnUnroutableId(runStore.findRunOnPrimary(runWhere, runArgs), {
70+
runParam: params.runParam,
71+
}));
6772

6873
if (!run) {
6974
return new Response("Run not found", { status: 404 });

0 commit comments

Comments
 (0)