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

@@ -1,13 +1,13 @@
import { rename as renameMesh } from "~/services/mesh/facade.js";
import { getStoredToken } from "~/services/auth/facade.js";
import { ApiError } from "~/services/api/facade.js";
import { bold, dim, green, icons } from "~/ui/styles.js";
import { EXIT } from "~/constants/exit-codes.js";
export async function rename(slug: string, newName: string): Promise<number> {
// Rename hits the account-scoped /api/my/meshes endpoint, which requires a
// web session token (~/.claudemesh/auth.json). Joining a mesh via invite
// does NOT create that token — it only writes a per-mesh apikey to
// config.json. Detect this case up front so the error is actionable.
// Rename requires a web session token (~/.claudemesh/auth.json). Joining
// a mesh via invite does NOT create that token — it only writes a per-mesh
// apikey to config.json. Detect this case up front so the error is actionable.
const auth = getStoredToken();
if (!auth) {
console.error(` ${icons.cross} Renaming a mesh requires a claudemesh.com account session.`);
@@ -22,6 +22,18 @@ export async function rename(slug: string, newName: string): Promise<number> {
console.log(` ${green(icons.check)} Renamed "${slug}" to "${newName}"`);
return EXIT.SUCCESS;
} catch (err) {
if (err instanceof ApiError) {
// Server returns { error: "..." } for actionable messages
// (not the owner, mesh missing, expired token). Surface the
// body — the bare HTTP code alone hides why it failed.
const body = err.body as { error?: string } | undefined;
const detail = body?.error ?? err.statusText;
console.error(` ${icons.cross} ${detail}`);
if (err.status === 401) return EXIT.AUTH_FAILED;
if (err.status === 403) return EXIT.PERMISSION_DENIED;
if (err.status === 404) return EXIT.NOT_FOUND;
return EXIT.INTERNAL_ERROR;
}
console.error(` ${icons.cross} Failed: ${err instanceof Error ? err.message : err}`);
return EXIT.INTERNAL_ERROR;
}