fix(rename): split 404 vs 403 + surface API error body (v1.19.2)
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

The rename route was collapsing "mesh doesn't exist" and "exists
but you don't own it" into a single 404 with body
{"error":"mesh not found or you are not the owner"}, and the CLI
was throwing that body away — the user only saw "API error 404:
Not Found", which is actively misleading when they have multiple
accounts and signed in to the wrong one.

Server: separate lookup-then-update. 404 only when the slug is
missing; 403 with an actionable message when the caller is not
the owner.

CLI: parse the {error} body off ApiError and print it instead of
the bare statusText. Map status codes to specific exit codes
(401 -> AUTH_FAILED, 403 -> PERMISSION_DENIED, 404 -> NOT_FOUND).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-03 14:44:22 +01:00
parent 79485898cf
commit f3649d761f
3 changed files with 43 additions and 14 deletions

View File

@@ -93,19 +93,36 @@ export async function PATCH(
return NextResponse.json({ error: "name too long (max 80 chars)" }, { status: 400 });
}
// Only the owner can rename. The slug is the public identifier; we
// match (slug, ownerUserId) to scope the update.
const [updated] = await db
.update(mesh)
.set({ name: newName })
.where(and(eq(mesh.slug, slug), eq(mesh.ownerUserId, payload.sub)))
.returning({ slug: mesh.slug, name: mesh.name });
// Look up the mesh first so we can distinguish "doesn't exist"
// (404) from "exists but you don't own it" (403). The CLI was
// collapsing both into a bare "API error 404" — unhelpful when
// the user has multiple accounts and signs in to the wrong one.
const [existing] = await db
.select({ ownerUserId: mesh.ownerUserId })
.from(mesh)
.where(eq(mesh.slug, slug))
.limit(1);
if (!updated) {
if (!existing) {
return NextResponse.json(
{ error: "mesh not found or you are not the owner" },
{ error: `mesh "${slug}" not found` },
{ status: 404 },
);
}
if (existing.ownerUserId !== payload.sub) {
return NextResponse.json(
{
error: `you are signed in as a different account than the owner of "${slug}". Run \`claudemesh logout && claudemesh login\` and pick the owning account.`,
},
{ status: 403 },
);
}
const [updated] = await db
.update(mesh)
.set({ name: newName })
.where(eq(mesh.slug, slug))
.returning({ slug: mesh.slug, name: mesh.name });
return NextResponse.json(updated);
}