Skip to content

fix: treat an empty Raider.IO customization as absent - #35

Merged
Erilla merged 1 commit into
mainfrom
fix/raiderio-empty-customization
Aug 10, 2026
Merged

fix: treat an empty Raider.IO customization as absent#35
Erilla merged 1 commit into
mainfrom
fix/raiderio-empty-customization

Conversation

@Erilla

@Erilla Erilla commented Aug 10, 2026

Copy link
Copy Markdown
Owner

eu/draenor/shurkle failed in test with upstream_schema_changed in 363 ms, before the fingerprint stage ran. Running the real parser against the live payload gives:

ZodError: path characterDetails.characterCustomizations.discord_profile
"Too small: expected string to have >=1 characters"

Raider.IO sends discord_profile: "" for a player who never set one. z.string().min(1) rejected it, the client raised schema_drift, and the handler mapped that to non-retryable upstream_schema_changed — so the search failed permanently, showing "The search could not be completed." for every character with an empty Discord field.

An empty value is an absent value, not structural change to the payload, which is what schema_drift is reserved for (normalize.ts says so in its own comment). It is now accepted and normalized to profileGuess: null, so nothing downstream searches on an empty alias.

Pre-existing since the MVP, not introduced by the fingerprint work.

New fixture plus a test, red with raiderio_schema_drift before the change, and the live shurkle payload now parses.

Worth noting the same class of risk remains for other min(1) upstream fields (user.name, main_character.name, main_character.path). I fixed only the field with a confirmed live failure rather than loosening the schema wholesale — but any of them arriving empty would fail a search the same permanent way.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Qh8Zb2HnaxebWrRLUMoAiv

Raider.IO sends discord_profile: "" for a player who never set one. The
schema required a non-empty string, so the parse threw, the client raised
schema_drift, and the run failed with non-retryable
upstream_schema_changed — "The search could not be completed" for every
such character, permanently.

An empty value is an absent value, not structural change to the payload.
Accept it and normalize it to no profile guess, so nothing downstream
searches on an empty alias.

Verified against the live eu/draenor/shurkle payload that produced the
failure in the test environment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qh8Zb2HnaxebWrRLUMoAiv
@Erilla
Erilla merged commit da965fe into main Aug 10, 2026
2 checks passed
@Erilla
Erilla deleted the fix/raiderio-empty-customization branch August 10, 2026 18:00
Erilla added a commit that referenced this pull request Aug 10, 2026
Follows #35. With the parse fixed, `eu/draenor/shurkle` got further and
then failed five attempts in ~130 ms each as `upstream_unavailable`,
with zero Blizzard requests — "Character data is temporarily
unavailable."

The cause, live:

```
GET /api/user/view-characters?name=shurkle
403 {"error":"Forbidden","message":"The requested user's profile is private and cannot be viewed.","errorCode":"profile_is_private"}
```

`shurkle` has no public Raider.IO ownership, so discovery falls back to
profile guesses — one of which is the character's own name. That name
belongs to a user whose profile is private. `responseFailure` mapped
every non-404 to `transient`, so a permanent answer about visibility was
retried as an outage and then failed the whole run.

- 403 is now a distinct `forbidden` failure kind.
- `resolveProfileGuess` treats `forbidden` like `not_found` and returns
`null`: an invisible profile yields no relationships. A guess is
speculative by nature, so failing the run on one is wrong regardless.
- `getCharacter` and `getClaimedCharacters` keep their current behaviour
for a 403 (retryable), since neither has a confirmed live case. Worth a
look separately — a 403 there is equally permanent.

New fixture with the real body, plus a test that was red with
`raiderio_transient`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Qh8Zb2HnaxebWrRLUMoAiv

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant