CopilotKit / CopilotKit/outpost

Add apiFetchJson that throws ApiError, and convert the ~120 unchecked res.ok call sites

Open
#227 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area: product roadmap roadmap: next
Dominant language
TypeScript
Stars
7
Forks
3
Avg merge
7d 16h
Merged PRs (30d)
15

Description

Origin: deferred bucket (c) from the Tier-3 cr-loop review of PR #167 (17 reviewers, 2026-08-19), merged as 55744ca. ~120 findings, one pattern. Filed so the deferred set is findable rather than living only in a review ledger.

The pattern

Across all 19 files #167 migrated, client code does:

const res = await apiFetch(url, { method: 'POST', body });
const data = await res.json();
setThing(data.thing);

No res.ok check. apiFetch deliberately does not throw on non-2xx (documented, and correct for a transport wrapper), and neither did the fetch it replaced — claim-verified as pre-existing: #167 changed only the call and the import, no error-handling lines. So this is not a regression, but #167 makes it more visible by removing the 403s that used to mask it.

Two distinct failure shapes:

  • Reads → the error body is written to state, so undefined reaches a .map/.filter/Object.values and the page crashes at render. Confirmed in sync/page.tsx:23,38,48 (systems.filter), onboarding/page.tsx:75 (metrics.stageCounts), sync/mappings/page.tsx:26 (an error body becomes the editable config), broadcasts-content.tsx:37.
  • Mutations → the failure is completely silent. Sharpest cases: accounts/page.tsx:66 optimistically writes the new owner and only attaches .catch(), so a 403/500 never reverts and the table shows an owner the server does not have. settings/team/page.tsx:92-128 — five of the highest-privilege writes in the app (change role, disable, remove member, resend invite, revoke invite) ignore the response entirely, so "Cannot remove yourself" and any 403 vanish. agents/new/page.tsx:21 — a failed create is a dead button. add-note-form.tsx:60 — a lost note, under a comment claiming "silently fail in mock mode" that no longer applies.

The fix, and why it is one helper rather than 120 patches

Two reviewers independently proposed the same thing: an apiFetchJson beside apiFetch that throws an ApiError carrying status, so every call site gets consistent handling and a 403 yields one recognisable "your session token expired, reload" message instead of 19 bespoke silences.

That is the extensible shape. Hand-patching 120 res.ok checks is neither finishable nor durable — it leaves call site 121 to be written wrong. apiFetch is already the single choke point every client mutation flows through, which is exactly the seam this belongs on.

Suggested sequencing: land apiFetchJson first, then convert call sites in small batches by area (tickets, settings, sync, agents), highest-privilege first — settings/team and accounts before the read-only pages.

Related

  • #225 — the lint guard proposed there (no-restricted-syntax on bare fetch() is what keeps new code on this seam once it exists.
  • #197 — none of these files call any logging function, so every failure above is also absent from error reporting.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the existing apiFetch implementation and review the cited call sites, especially settings/team/page.tsx, accounts/page.tsx, sync/page.tsx, and agents/new/page.tsx. Add the apiFetchJson helper beside apiFetch, then convert the remaining unchecked response sites in batches by area. Done means the roughly 120 sites use consistent error handling, with ApiError status available for failures.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, frontend
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
43/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.