JuliusBrussee/caveman · error · Error

eval evidence import failed (${response.status}): ${JSON.str

Error message

eval evidence import failed (${response.status}): ${JSON.stringify(body)}

What it means

The eval-evidence batch POST to /api/v1/eval-evidence/batches returned a non-2xx status. The CLI surfaces the HTTP status code and the parsed response body verbatim, so the server's own error message is always visible. Auth, CSRF, project-scope, and per-record validation failures all surface through this single throw.

Source

Thrown at packages/cli/src/index.ts:17181

  if (items.length === 0) throw new Error("eval evidence file contains no records");

  const cfg = await config();
  const project = flagFrom(argv, "--project", cfg.projectId ?? "");
  const response = await fetch(`${cfg.baseURL}/api/v1/eval-evidence/batches`, {
    method: "POST",
    headers: {
      authorization: `Bearer ${cfg.token}`,
      "content-type": "application/json",
      "x-cave-csrf": "cli",
    },
    body: JSON.stringify({
      ...(project ? { project_id: project } : {}),
      dry_run: argv.includes("--dry-run"),
      items,
    }),
  });
  const body = await response.json();
  if (!response.ok) throw new Error(`eval evidence import failed (${response.status}): ${JSON.stringify(body)}`);
  print(body);
}

// ---------------------------------------------------------------------------
// Signed usage receipts: air-gapped export + offline verification.
//
// A receipt is a per-(org,project,day) aggregate, Ed25519-signed and chained by
// prev_receipt_hash. `verify` recomputes each canonical hash, checks the
// signature against the published public key, and walks the chain — all offline,
// so finance (either party) can re-derive trust without contacting Caveman. It is
// the cross-language counterpart of cloud/metering's VerifyChain.
// ---------------------------------------------------------------------------

type ReceiptSignature = { alg: string; key_id: string; sig: string };
type ReceiptScope = { org_hash: string; project_hash: string };
type ReceiptOptimizer = { optimizer_id_hash: string; requests_optimized: number };
type Receipt = {
  schema: string;

View on GitHub (pinned to 5184b3d11a)

Solutions

  1. Read the embedded body — it carries the server's actual reason
  2. On 401/403: run `caveman login` again and retry the import
  3. On 400/404: fix the project uuid or the record fields named in the body
  4. For persistent failures: compare CLI and server versions for validation drift
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight auth and project scope before the big upload.
const me = await fetch(`${cfg.baseURL}/api/v1/me`, { headers: { authorization: `Bearer ${cfg.token}` } });
if (!me.ok) throw new Error('token invalid — re-login before import');

Try / catch

try {
  await postBatch(items);
} catch (e) {
  const msg = (e as Error).message;
  const status = Number(/\((\d+)\)/.exec(msg)?.[1] ?? 0);
  if (status === 401) {
    await relogin();
    await postBatch(items); // idempotent retry once auth is refreshed
  } else throw e;
}

Prevention

When it happens

Trigger: 401/403 from an expired or wrong bearer token; 400 when the server rejects record shapes or the project_id; 404 for an unknown project uuid; 413/422 when server-side caps trip; an intermediary proxy replacing the body.

Common situations: Token expiring between login and import in long CI runs; typo'd --project uuid; server updated with stricter validation than the CLI expects; corporate proxies mangling responses.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18). Data as JSON: /api/errors/94bef159b6aa1488. Report an issue: GitHub.