CopilotKit / CopilotKit/outpost
Add apiFetchJson that throws ApiError, and convert the ~120 unchecked res.ok call sites
Nobody has claimed this yet.
- 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
undefinedreaches a.map/.filter/Object.valuesand the page crashes at render. Confirmed insync/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:66optimistically 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
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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