Repository navigation
fix(api): keep the service code and message on User Sync errors - #108
Merged
Merged
Conversation
A 409 OutOfSyncError from POST /v1/sync surfaced as message "409 " (HTTP/2 has no statusText) with no code. The User Sync facade now reads the service error from the Gateway (details.error) or backend (error) envelope and sets `code` and appends the server message on the same QuranHttpError, with a default message for an OutOfSyncError that has none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Contributor
size-limit report 📦
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In a production end-to-end test with
@quranjs/api@3.16.0, a 409OutOfSyncErrorfromclient.auth.v1.sync.pushMutations(...)came back with message"409 "and no useful code (the only code-like value was the Gateway's genericpayload.type: "service_error").isUserSyncOutOfSyncErrorstill detected it correctly.Root cause
QuranHttpErrorbuilds its message as`${status} ${statusText}`and has nocodefield. Over HTTP/2 there is no status text, so the message is"409 ". The server's code and reason are only inpayload(indetails.errorfor the Gateway envelope, inerrorfor the direct backend envelope).Fix and why it lives in the User Sync facade
The generic cause is shared by every
QuranHttpError. I did not change the shared error, for these reasons:"<status> <statusText>"is pinned by the HTTP error contract tests (test/http-error.test.ts), and the content-scope 403 hint is appended to it (test/content-scopes.test.tsmatches^403 Forbidden\. ...). Changing every error's message is a wider behavior change than a patch should make.QuranHttpErrorstays generic and a domain helper (getAppStateErrorCode) reads the service code from the payload.So
createUserSyncFacadenow passes any error from/v1/syncthroughwithUserSyncErrorDetails, which updates the sameQuranHttpErrorin place:codeis set fromdetails.error.code(Gateway) orerror.code(backend).: <message>is appended to the trimmed base message. It uses the service message, then the top-levelmessage. If neither exists and the error is anOutOfSyncError, it usesOut of sync: lastMutationAt is stale; pull and retry.QuranHttpError, are left unchanged.instanceof,status,headersandpayloadare untouched.isUserSyncOutOfSyncErroris unchanged and still returns false forUniqueViolationError409s.pullAllMutationsrestart logic is unaffected.UserSyncOutOfSyncErrorgains an optionalcode?: "OutOfSyncError", which is an additive change.OutOfSyncError, no statusText"409 ", nocode"409: Invalid lastMutationAt, please re-sync your data and try again.",code: "OutOfSyncError"OutOfSyncError, no statusText"409 ", nocodeOutOfSyncErrorwith no message"409 ", nocode"409: Out of sync: lastMutationAt is stale; pull and retry.",code: "OutOfSyncError"UniqueViolationError"409 Conflict""409 Conflict: You already have a collection with this name",code: "UniqueViolationError"Tests
A new case in
packages/api/test/user-sync.test.tscovers message and code for both envelopes and for the no-message default. It also checks thatUniqueViolationErrorand errors without a code behave as described, and that detection results are unchanged. It uses an injectedfetchbecause MSW fills in a defaultstatusText, which would hide the"409 "case.Local checks: Prettier on changed files,
tsc --noEmit(typecheck), andeslintforpackages/apiare clean, andtest/user-sync.test.tspasses (10/10).🤖 Generated with Claude Code