Skip to content

Commit 14920ce

Browse files
d-csclaude
andauthored
fix(webapp): downgrade expected user-input error logs to warn (#3523)
`dac9c83bd` added `ignoreErrors: /^ServiceValidationError(?::|$)/` in `apps/webapp/sentry.server.ts` to drop SVEs before they reach Sentry. The filter only matches when the captured event's *type* is `ServiceValidationError`, but nine call sites in the webapp catch SVE (and analogous user-input error types — `OutOfEntitlementError`, `CreateDeclarativeScheduleError`, `QueryError`) and call `logger.error("wrapper message", { error: e })` *before* the type check. The captured event is then titled with the wrapper message, with the inner error buried in `extra.error` — invisible to the SDK filter. Result: a steady stream of expected user-input failures escalating as `error`-level events when they should be `warn`. Each catch block now type-discriminates first, logs expected types at `warn`, and keeps unknown-error fall-throughs at `error`. For service sites that wrap into SVE (`createBackgroundWorker`, `createDeploymentBackgroundWorkerV4`), the inner error is logged at `error` before wrapping — mirrors the `waitpointCompletionPacket.server.ts` pattern from `dac9c83bd`. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a8966a4 commit 14920ce

10 files changed

Lines changed: 127 additions & 43 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Stop nine catch sites in the webapp from escalating expected user-input
7+
failures (`ServiceValidationError`, `OutOfEntitlementError`,
8+
`CreateDeclarativeScheduleError`, `QueryError`) as `error`-level events.
9+
Type-discriminate before logging; downgrade the user-facing branches to
10+
`warn` while keeping unknown-error fall-throughs at `error`.

apps/webapp/app/routes/api.v1.deployments.$deploymentId.background-workers.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,20 @@ export async function action({ request, params }: ActionFunctionArgs) {
6060
{ status: 200 }
6161
);
6262
} catch (e) {
63-
logger.error("Failed to create background worker", { error: e });
64-
63+
// Customer-facing validation failures (invalid task config, customer cron
64+
// expression, etc.). The handler returns 4xx with the message; system
65+
// handles it gracefully, no alert needed.
6566
if (e instanceof ServiceValidationError) {
67+
logger.warn("Failed to create background worker", { error: e.message });
6668
return json({ error: e.message }, { status: e.status ?? 400 });
67-
} else if (e instanceof CreateDeclarativeScheduleError) {
69+
}
70+
if (e instanceof CreateDeclarativeScheduleError) {
71+
logger.warn("Failed to create background worker", { error: e.message });
6872
return json({ error: e.message }, { status: 400 });
6973
}
7074

75+
logger.error("Failed to create background worker", { error: e });
76+
7177
return json({ error: "Failed to create background worker" }, { status: 500 });
7278
}
7379
}

apps/webapp/app/routes/api.v1.projects.$projectRef.background-workers.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,20 @@ export async function action({ request, params }: ActionFunctionArgs) {
5858
{ status: 200 }
5959
);
6060
} catch (e) {
61-
logger.error("Failed to create background worker", { error: JSON.stringify(e) });
62-
61+
// Customer-facing validation failures (invalid task config, customer cron
62+
// expression, etc.). The handler returns 4xx with the message; system
63+
// handles it gracefully, no alert needed.
6364
if (e instanceof ServiceValidationError) {
65+
logger.warn("Failed to create background worker", { error: e.message });
6466
return json({ error: e.message }, { status: 400 });
65-
} else if (e instanceof CreateDeclarativeScheduleError) {
67+
}
68+
if (e instanceof CreateDeclarativeScheduleError) {
69+
logger.warn("Failed to create background worker", { error: e.message });
6670
return json({ error: e.message }, { status: 400 });
6771
}
6872

73+
logger.error("Failed to create background worker", { error: e });
74+
6975
return json({ error: "Failed to create background worker" }, { status: 500 });
7076
}
7177
}

apps/webapp/app/routes/api.v1.query.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,25 @@ const { action, loader } = createActionApiRoute(
6161
});
6262

6363
if (!queryResult.success) {
64-
const message =
65-
queryResult.error instanceof QueryError
66-
? queryResult.error.message
67-
: "An unexpected error occurred while executing the query.";
64+
// QueryError surfaces customer SQL problems (invalid syntax,
65+
// unsupported construct). Returned to the caller as 400; system
66+
// handles it gracefully, no alert needed.
67+
if (queryResult.error instanceof QueryError) {
68+
logger.warn("Query API error", {
69+
error: queryResult.error.message,
70+
query,
71+
});
72+
return json({ error: queryResult.error.message }, { status: 400 });
73+
}
6874

6975
logger.error("Query API error", {
7076
error: queryResult.error,
7177
query,
7278
});
7379

7480
return json(
75-
{ error: message },
76-
{ status: queryResult.error instanceof QueryError ? 400 : 500 }
81+
{ error: "An unexpected error occurred while executing the query." },
82+
{ status: 500 }
7783
);
7884
}
7985

apps/webapp/app/routes/api.v1.tasks.batch.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,18 +127,26 @@ const { action, loader } = createActionApiRoute(
127127

128128
return json(batch, { status: 202, headers: $responseHeaders });
129129
} catch (error) {
130+
// Customer-facing validation/quota failures (invalid batch shape,
131+
// entitlements exhausted). The handler returns 422 with the message;
132+
// system handles it gracefully, no alert needed.
133+
if (error instanceof ServiceValidationError) {
134+
logger.warn("Batch trigger error", { error: error.message });
135+
return json({ error: error.message }, { status: 422 });
136+
}
137+
if (error instanceof OutOfEntitlementError) {
138+
logger.warn("Batch trigger error", { error: error.message });
139+
return json({ error: error.message }, { status: 422 });
140+
}
141+
130142
logger.error("Batch trigger error", {
131143
error: {
132144
message: (error as Error).message,
133145
stack: (error as Error).stack,
134146
},
135147
});
136148

137-
if (error instanceof ServiceValidationError) {
138-
return json({ error: error.message }, { status: 422 });
139-
} else if (error instanceof OutOfEntitlementError) {
140-
return json({ error: error.message }, { status: 422 });
141-
} else if (error instanceof Error) {
149+
if (error instanceof Error) {
142150
return json(
143151
{ error: "Something went wrong" },
144152
{ status: 500, headers: { "x-should-retry": "false" } }

apps/webapp/app/routes/api.v2.tasks.batch.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,26 @@ const { action, loader } = createActionApiRoute(
144144
headers: $responseHeaders,
145145
});
146146
} catch (error) {
147+
// Customer-facing validation/quota failures (invalid batch shape,
148+
// entitlements exhausted). The handler returns 422 with the message;
149+
// system handles it gracefully, no alert needed.
150+
if (error instanceof ServiceValidationError) {
151+
logger.warn("Batch trigger error", { error: error.message });
152+
return json({ error: error.message }, { status: 422 });
153+
}
154+
if (error instanceof OutOfEntitlementError) {
155+
logger.warn("Batch trigger error", { error: error.message });
156+
return json({ error: error.message }, { status: 422 });
157+
}
158+
147159
logger.error("Batch trigger error", {
148160
error: {
149161
message: (error as Error).message,
150162
stack: (error as Error).stack,
151163
},
152164
});
153165

154-
if (error instanceof ServiceValidationError) {
155-
return json({ error: error.message }, { status: 422 });
156-
} else if (error instanceof OutOfEntitlementError) {
157-
return json({ error: error.message }, { status: 422 });
158-
} else if (error instanceof Error) {
166+
if (error instanceof Error) {
159167
return json(
160168
{ error: error.message },
161169
{ status: 500, headers: { "x-should-retry": "false" } }

apps/webapp/app/routes/api.v3.batches.$batchId.items.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,22 @@ export async function action({ request, params }: ActionFunctionArgs) {
8888

8989
return json(result, { status: 200 });
9090
} catch (error) {
91+
// Customer-facing validation failures (invalid item shape, invalid JSON
92+
// in the streamed body). The handler returns 4xx with the message;
93+
// system handles it gracefully, no alert needed.
94+
if (error instanceof ServiceValidationError) {
95+
logger.warn("Stream batch items error", { batchId, error: error.message });
96+
return json({ error: error.message }, { status: 422 });
97+
}
98+
99+
if (error instanceof Error && error.message.includes("Invalid JSON")) {
100+
logger.warn("Stream batch items error: invalid JSON", {
101+
batchId,
102+
error: error.message,
103+
});
104+
return json({ error: error.message }, { status: 400 });
105+
}
106+
91107
logger.error("Stream batch items error", {
92108
batchId,
93109
error: {
@@ -96,14 +112,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
96112
},
97113
});
98114

99-
if (error instanceof ServiceValidationError) {
100-
return json({ error: error.message }, { status: 422 });
101-
} else if (error instanceof Error) {
102-
// Check for stream parsing errors (e.g. invalid JSON)
103-
if (error.message.includes("Invalid JSON")) {
104-
return json({ error: error.message }, { status: 400 });
105-
}
106-
115+
if (error instanceof Error) {
107116
return json({ error: error.message }, { status: 500 });
108117
}
109118

apps/webapp/app/routes/api.v3.batches.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,26 @@ const { action, loader } = createActionApiRoute(
172172
);
173173
}
174174

175+
// Customer-facing validation/quota failures (invalid batch shape,
176+
// entitlements exhausted). The handler returns 422 with the message;
177+
// system handles it gracefully, no alert needed.
178+
if (error instanceof ServiceValidationError) {
179+
logger.warn("Create batch error", { error: error.message });
180+
return json({ error: error.message }, { status: error.status ?? 422 });
181+
}
182+
if (error instanceof OutOfEntitlementError) {
183+
logger.warn("Create batch error", { error: error.message });
184+
return json({ error: error.message }, { status: 422 });
185+
}
186+
175187
logger.error("Create batch error", {
176188
error: {
177189
message: (error as Error).message,
178190
stack: (error as Error).stack,
179191
},
180192
});
181193

182-
if (error instanceof ServiceValidationError) {
183-
return json({ error: error.message }, { status: 422 });
184-
} else if (error instanceof OutOfEntitlementError) {
185-
return json({ error: error.message }, { status: 422 });
186-
} else if (error instanceof Error) {
194+
if (error instanceof Error) {
187195
return json(
188196
{ error: error.message },
189197
{ status: 500, headers: { "x-should-retry": "false" } }

apps/webapp/app/v3/services/createBackgroundWorker.server.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,27 @@ export class CreateBackgroundWorkerService extends BaseService {
146146
);
147147

148148
if (schedulesError) {
149+
if (schedulesError instanceof ServiceValidationError) {
150+
// Customer schedule config (typically invalid cron). Surface to
151+
// client via the rethrow; system returns gracefully.
152+
logger.warn("Error syncing declarative schedules", {
153+
error: schedulesError.message,
154+
backgroundWorker,
155+
environment,
156+
});
157+
throw schedulesError;
158+
}
159+
160+
// Wrapping the underlying error into a ServiceValidationError below
161+
// would otherwise hide it once the SDK-level filter drops SVEs; log at
162+
// error so the underlying cause stays visible. Mirrors the
163+
// waitpointCompletionPacket.server.ts pattern from dac9c83bd.
149164
logger.error("Error syncing declarative schedules", {
150165
error: schedulesError,
151166
backgroundWorker,
152167
environment,
153168
});
154169

155-
if (schedulesError instanceof ServiceValidationError) {
156-
throw schedulesError;
157-
}
158-
159170
throw new ServiceValidationError("Error syncing declarative schedules");
160171
}
161172

apps/webapp/app/v3/services/createDeploymentBackgroundWorkerV4.server.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,26 @@ export class CreateDeploymentBackgroundWorkerServiceV4 extends BaseService {
139139
);
140140

141141
if (schedulesError) {
142+
if (schedulesError instanceof ServiceValidationError) {
143+
// Customer schedule config (typically invalid cron). Surface to
144+
// client via the rethrow; system returns gracefully.
145+
logger.warn("Error syncing declarative schedules", {
146+
error: schedulesError.message,
147+
});
148+
149+
await this.#failBackgroundWorkerDeployment(deployment, schedulesError);
150+
throw schedulesError;
151+
}
152+
153+
// Wrapping the underlying error into a ServiceValidationError below
154+
// would otherwise hide it once the SDK-level filter drops SVEs; log at
155+
// error so the underlying cause stays visible. Mirrors the
156+
// waitpointCompletionPacket.server.ts pattern from dac9c83bd.
142157
logger.error("Error syncing declarative schedules", {
143158
error: schedulesError,
144159
});
145160

146-
const serviceError =
147-
schedulesError instanceof ServiceValidationError
148-
? schedulesError
149-
: new ServiceValidationError("Error syncing declarative schedules");
161+
const serviceError = new ServiceValidationError("Error syncing declarative schedules");
150162

151163
await this.#failBackgroundWorkerDeployment(deployment, serviceError);
152164

0 commit comments

Comments
 (0)