Skip to content

fix(avatar): classify image-gen failures + render actionable detail (#957) - #976

Merged
vybe merged 1 commit into
devfrom
fix/957-avatar-error-clarity
Jun 1, 2026
Merged

fix(avatar): classify image-gen failures + render actionable detail (#957)#976
vybe merged 1 commit into
devfrom
fix/957-avatar-error-clarity

Conversation

@dolho

@dolho dolho commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Avatar Generate dialog showed only "Failed to generate avatar" with no diagnostic info. Operators couldn't tell whether the failure was a missing API key, an upstream rate limit, a safety-filter rejection, or a network timeout.

Two root causes:

  1. Backend: returned the raw upstream exception string as the HTTP detail and used hardcoded 422. Several real failure modes (nginx 504 with HTML body, network abort) strip the JSON detail entirely.
  2. Frontend: bare axios (Invariant security: Fix token logging and add HTML reports to gitignore #7 violation) with no per-status fallback chain — when detail was missing it dropped to a hardcoded generic message.

Backend

  • ImageGenerationResult.error_kind — coarse classification (not_configured | invalid_input | safety_filter | rate_limited | upstream_error | timeout | unknown) set on every failure path.
  • _classify_exception() maps httpx + RuntimeError exceptions to a kind.
  • Catch blocks now use structured logging via extra={...} so Vector indexes agent_name, error_kind, exception_type, etc. as fields instead of grepping a string.
  • _AVATAR_ERROR_HTTP map → kind to (HTTP status, friendly detail). generate_avatar and regenerate_avatar use the map instead of hardcoded 422 + raw exception text. Service-not-available early-exit uses the same friendly text.

Frontend

  • AvatarGenerateModal.vue switched from bare axios to @/api and bumped per-request timeout to 180s (image gen can take >30s).
  • describeAvatarError(err, verb) falls back gracefully on 502/503/504/no-response cases so the user always gets a directional message.

Tests

7 new cases in tests/unit/test_image_generation_service.py cover _classify_exception and the error_kind field. All 40 unit tests in the file pass.

Related to #957

Test plan

Verified live + via static-mock analysis:

  • Live: not_configuredPOST /api/agents/.../avatar/generate against empty GEMINI_API_KEY returns 501 with detail "Avatar generation isn't configured on this server. Ask an admin to set GEMINI_API_KEY." Dialog renders that text.
  • safety_filter mock (422 with friendly detail) — dialog renders the safety-filter wording.
  • timeout / 504 mock — describeAvatarError returns "Avatar generate timed out — please retry."
  • network abort (no err.response) — returns "Network error while trying to generate avatar — check your connection and retry."
  • 502/503 — returns "Image generation service is unavailable right now — please retry in a few minutes."
  • .venv/bin/pytest tests/unit/test_image_generation_service.py — 40 passed (33 existing + 7 new).

🤖 Generated with Claude Code

…957)

Avatar Generate dialog showed only "Failed to generate avatar" with no
diagnostic info — operators couldn't tell whether the failure was a
missing API key, an upstream rate limit, a safety-filter rejection, or
a network timeout. Root causes:

- Backend returned the raw upstream exception string as the HTTP detail.
  In several real failure modes (nginx 504 with HTML body, network
  abort) the frontend got no JSON detail at all and fell back to a
  hardcoded generic message.
- Frontend used bare `axios` instead of the shared `@/api` client
  (Invariant #7), with no per-status fallback chain.

Backend:
- `ImageGenerationResult.error_kind` — coarse classification
  (`not_configured` | `invalid_input` | `safety_filter` | `rate_limited`
  | `upstream_error` | `timeout` | `unknown`) set on every failure path.
- `_classify_exception()` maps httpx + RuntimeError exceptions to a kind.
- Catch blocks now use structured logging via `extra={...}` so Vector
  indexes agent_name, error_kind, exception_type, etc. as fields.
- `_AVATAR_ERROR_HTTP` map → kind to (HTTP status, friendly detail).
  `generate_avatar` and `regenerate_avatar` use the map instead of
  hardcoded 422 + raw exception text. Service-not-available early-exit
  uses the same friendly text.

Frontend:
- `AvatarGenerateModal.vue` switched from bare `axios` to `@/api` and
  bumped the per-request timeout to 180s (image gen can take >30s).
- `describeAvatarError(err, verb)` falls back gracefully on 502/503/504
  and no-response cases so the user gets a directional message even
  when the upstream strips the JSON detail.

Tests:
- 7 new cases in `tests/unit/test_image_generation_service.py` cover
  `_classify_exception` and the `error_kind` field default.

Related to #957

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dolho
dolho requested a review from vybe May 29, 2026 13:17

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — verified _classify_exception substrings match the strings the service actually raises (Gemini API error {status}, no image data), and the 429-before-4xx check ordering is correct so rate-limit classification wins. Frontend axios→@/api switch fixes Invariant #7 and is required for the fallback chain. CI green, 7 new tests. Validated via /validate-pr.

@vybe
vybe merged commit 07d52d8 into dev Jun 1, 2026
14 checks passed
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.

2 participants