JuliusBrussee/caveman · error · Error

result.error?.message ?? `local scan sync failed

Error message

result.error?.message ?? `local scan sync failed (${response.status})`

What it means

Thrown when a local-scan sync POST completes but the response does not describe a successful delivery: either the server returned an error body (its `error.message` is surfaced) or the payload failed shape checks (status must be "completed", source "local_scan", basis "inferred", and a non-empty string `id`). It signals the sync import was not accepted.

Solutions

  1. Read the surfaced `error.message` from the response body shown in the thrown error for the server's reason.
  2. Verify CLI and gateway versions are compatible (response contract: status=completed, source=local_scan, basis=inferred).
  3. Check that the target project exists and the scan payload passes server validation.
  4. If a proxy interferes, hit the gateway directly and inspect the raw response.

Example fix

// before: stale CLI expecting old response shape
throw new Error(result.error?.message ?? `local scan sync failed (${response.status})`)
// after: upgrade CLI to match current gateway response contract
npm i -g @caveman/cli@latest && caveman sync
Defensive patterns

Strategy: validation

Validate before calling

// Validate the expected response contract before treating sync as done
function isDelivered(r) {
  return r && r.status === "completed" && r.source === "local_scan" &&
    r.basis === "inferred" && typeof r.id === "string" && r.id.length > 0;
}

Type guard

function isSyncResult(v: unknown): v is { status: "completed"; source: "local_scan"; basis: "inferred"; id: string; project_id?: string } {
  const r = v as Record<string, unknown>;
  return r.status === "completed" && r.source === "local_scan" &&
    r.basis === "inferred" && typeof r.id === "string" && r.id.length > 0;
}

Try / catch

try {
  await syncLocalScan();
} catch (e) {
  console.error(`Local scan sync rejected: ${e.message}`); // server error.message is surfaced first
  // inspect CLI/gateway version compatibility, then retry after fixing
}

Prevention

When it happens

Trigger: Syncing local scan results when the server rejects the payload (validation error), returns a non-completed status, or responds with an unexpected JSON shape missing `id`/`project_id`.

Common situations: Gateway version mismatch where the response schema changed (e.g. source/basis values differ); server-side validation rejecting the scan batch; proxy returning an HTML error page parsed as {}; project not provisioned server-side.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/80c43cb8fa10bec4. Report an issue: GitHub.

Appendix: source

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

    body: JSON.stringify(state.payload),
  });
  const result = (await response.json().catch(() => ({}))) as {
    id?: string;
    project_id?: string;
    status?: string;
    source?: string;
    basis?: string;
    error?: { message?: string };
  };
  if (
    !response.ok
    || result.status !== "completed"
    || result.source !== "local_scan"
    || result.basis !== "inferred"
    || typeof result.id !== "string"
    || !result.id
  ) {
    throw new Error(result.error?.message ?? `local scan sync failed (${response.status})`);
  }
  const delivered: LocalScanState = {
    ...state,
    delivered: {
      import_id: result.id,
      project_id: typeof result.project_id === "string" ? result.project_id : cfg.projectId ?? "",
      organization_id: cfg.organizationId ?? "",
      base_url: cfg.baseURL,
      delivered_at: new Date().toISOString(),
    },
  };
  mkdirSync(dirname(localScanStatePath()), { recursive: true });
  writeFileSync(localScanStatePath(), JSON.stringify(delivered, null, 2) + "\n", { mode: 0o600 });
  return { kind: "synced", importId: result.id, dashboard: deriveLocalScanDashboardUrl(cfg.baseURL) };
}

function localScanSyncLine(out: Extract<LocalScanSyncOutcome, { kind: "synced" }>): string {
  const destination = out.dashboard ? ` → ${out.dashboard}` : "";

View on GitHub (pinned to 3ee70a1026)