fix(agent-api): wrap all six agent routes in a catch-all error handler

Every /api/owner/* and /api/client/* route wraps its body in try/catch;
none of the six /api/agent/* routes did, so an unexpected failure (e.g. a
PocketBase validation error partway through the subtasks loop) surfaced as
Next's default non-JSON 500 instead of the jsonError shape this surface
otherwise guarantees everywhere else. A caller retrying after an opaque
500 mid-loop could also create duplicate subtasks for ones that already
succeeded.

Follows the exact pattern already used by
src/app/api/owner/cards/[id]/move/route.ts: requireAgentToken and the
initial body-parse/validation stay outside the try (they touch no
PocketBase and already return correctly-shaped errors); everything after
is wrapped, with a route-specific console.error prefix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-09-26 10:23:20 +02:00
parent e486bd818c
commit 76a012bb43
5 changed files with 223 additions and 196 deletions

View file

@ -37,6 +37,7 @@ export async function PATCH(
return jsonError("A destination column name is required", 400);
}
try {
const pb = await getAdminPb();
const card = await pb.collection("cards").getOne<CardRecord>(cardId).catch(() => null);
if (!card || card.archived) return jsonError("Card not found", 404);
@ -100,4 +101,8 @@ export async function PATCH(
column: destination.name,
accepted: updated.accepted,
});
} catch (error) {
console.error("[agent-api] move card failed", error);
return jsonError("Something went wrong", 500);
}
}

View file

@ -25,6 +25,8 @@ export async function GET(
if (denied) return denied;
const { cardId } = await context.params;
try {
const pb = await getAdminPb();
const card = await pb.collection("cards").getOne<CardRecord>(cardId).catch(() => null);
@ -85,4 +87,8 @@ export async function GET(
authored_at: commit.authored_at,
})),
});
} catch (error) {
console.error("[agent-api] get card failed", error);
return jsonError("Something went wrong", 500);
}
}

View file

@ -41,6 +41,7 @@ export async function POST(
return jsonError("At least one non-empty title is required", 400);
}
try {
const pb = await getAdminPb();
const card = await pb.collection("cards").getOne<CardRecord>(cardId).catch(() => null);
if (!card || card.archived) return jsonError("Card not found", 404);
@ -65,4 +66,8 @@ export async function POST(
}
return jsonOk({ created }, 201);
} catch (error) {
console.error("[agent-api] create subtask failed", error);
return jsonError("Something went wrong", 500);
}
}

View file

@ -18,6 +18,8 @@ export async function GET(
if (denied) return denied;
const { projectId } = await context.params;
try {
const pb = await getAdminPb();
const project = await pb.collection("projects").getOne(projectId).catch(() => null);
@ -69,4 +71,8 @@ export async function GET(
.filter((c): c is NonNullable<typeof c> => c !== null);
return jsonOk(out);
} catch (error) {
console.error("[agent-api] list project cards failed", error);
return jsonError("Something went wrong", 500);
}
}

View file

@ -1,6 +1,6 @@
import { getAdminPb } from "@/lib/pb/admin";
import { requireAgentToken } from "@/lib/agent/auth";
import { jsonOk } from "@/lib/http/json";
import { jsonError, jsonOk } from "@/lib/http/json";
import type { ClientRecord, ProjectRecord } from "@/lib/pb/types";
/**
@ -14,6 +14,7 @@ export async function GET(request: Request): Promise<Response> {
const denied = requireAgentToken(request);
if (denied) return denied;
try {
const pb = await getAdminPb();
const [projects, clients] = await Promise.all([
@ -39,4 +40,8 @@ export async function GET(request: Request): Promise<Response> {
.filter((p): p is NonNullable<typeof p> => p !== null);
return jsonOk(out);
} catch (error) {
console.error("[agent-api] list projects failed", error);
return jsonError("Something went wrong", 500);
}
}