feat(exception): add typed UserConflictException and conflict error codes - #374
Merged
Merged
Conversation
…odes Callers that pass failOnConflict on updateEmail/updatePhone had nothing to catch: any conflict surfaced as a generic ServerCommonException, so the only option was string matching on getCode(). - ErrorCode gains USER_UPDATE_CONFLICT (E111127, the management conflict code added in descope/backend#2657) and AUTH_USER_UPDATE_CONFLICT (E062125, the onetimeservice conflict returned from auth flows), plus USER_ALREADY_EXISTS, USER_NOT_FOUND, BAD_REQUEST and VALIDATION_FAILURE for reference. - Responses carrying either conflict code now throw UserConflictException. The generic bad request and validation codes are deliberately not mapped, they are reused across unrelated endpoints. - README documents the silent merge risk and how to guard against it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136Cgn4PC3tt7RomWgvKkEY
Contributor
🐕 Shuni ReviewThis PR adds typed user-conflict exceptions, exposes error-code constants, and tests conflict and merge behavior. Actionable comments posted: 2
Merge risk: 🟢 Low: the verified issues affect functional-test reliability and cleanup rather than the exception dispatch logic. See inline comments for details. 🤖 Model: Review scope: Full review Reviewed files (5)
🐕 Review complete — View session on Shuni Portal 🐾 🤖 Model: |
aviadl
enabled auto-merge (squash)
September 15, 2026 12:23
slavikm
approved these changes
Sep 15, 2026
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.
Related issues
Part of https://github.com/descope/etc/issues/18009 and https://github.com/descope/etc/issues/18488
Backend counterpart: https://github.com/descope/backend/pull/2657
Problem
1.5.0 shipped
failOnConflictonupdateEmail/updatePhone(#360), but a caller who sets it has nothing to catch. A conflict arrives as a genericServerCommonException, so the only way to detect it is string matching ongetCode(), and the relevant constants are not exposed. The default behavior it guards against is severe: withoutfailOnConflictthe server merges the colliding user into the one being updated and deletes it, returning 200.What this adds
ErrorCode:USER_UPDATE_CONFLICT(E111127) - the management conflict code introduced in descope/backend#2657, returned with HTTP 409 byupdate/emailandupdate/phone.AUTH_USER_UPDATE_CONFLICT(E062125) - onetimeservice'sUpdateUserConflict, returned from auth flows and already live today. PlusUSER_ALREADY_EXISTS(E062107),USER_NOT_FOUND(E112102),BAD_REQUEST(E011001) andVALIDATION_FAILURE(E011003).UserConflictException- new typed exception, same shape asRateLimitExceededException.AbstractProxyImpl- throws it when the response carries either conflict code.On the four codes named in the issues
They were checked against backend
mainrather than taken at face value, since the issue text guesses at some of them:E062107UserAlreadyExistsE011001BadRequest, "Request is malformed"E011003ValidationFailure(protoc-gen-validate)E112102UserNotFound- the issue describes it as "login ID already exists", which it is notThe code that actually means "this identifier belongs to another user" for these endpoints is
E111127.Not in this PR
updateLoginId(loginId, newLoginId, failOnConflict). The backendUpdateUserLoginIDRequesthas no such field today (explicitly out of scope of backend#2657), so the argument would be silently dropped and callers would believe they were protected when they are not. It needs the backend change first.Tests
AbstractProxyImplTest: both conflict codes surface asUserConflictExceptionwith the right code and message; the four generic codes do not.UserServiceImplTest: two functional tests covering the real conflict against a live project - withfailOnConflictthe call fails and both users survive, without it the call returns 200 and the other user's userId is gone. Both pass today. The conflict assertion tightens toUserConflictException+E111127automatically once backend#2657 is deployed; until then it accepts the current generic error (marked with a comment to drop that branch after the deploy).Full
mvn testwas run against a stashed baseline as well: identical failure sets, so no new failures. The 5 failures present in both are environmental (project region env-var test, and functional SSO/auth tests failing onE074106"license does not match requirement" for the test project).mvn checkstyle:checkis clean.All changes are additive and backward compatible.
🤖 Generated with Claude Code
https://claude.ai/code/session_0136Cgn4PC3tt7RomWgvKkEY