Skip to content

[FIX] Surface non_field_errors and name resources in 404s in the central DRF error path#2099

Merged
chandrasekharan-zipstack merged 8 commits into
mainfrom
fix/fe-non-field-errors
Jun 24, 2026
Merged

[FIX] Surface non_field_errors and name resources in 404s in the central DRF error path#2099
chandrasekharan-zipstack merged 8 commits into
mainfrom
fix/fe-non-field-errors

Conversation

@chandrasekharan-zipstack

@chandrasekharan-zipstack chandrasekharan-zipstack commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

What

Two complementary fixes in the central DRF error path so API errors reach the user with usable text instead of silently vanishing or showing a context-free message.

1. Surface non_field_errors as a toast (frontend)

useExceptionHandler now surfaces validation errors that aren't bound to a form field (non_field_errors, or errors with no attr) as a toast, in addition to the existing inline field-error rendering.

When a form passes setBackendErrors, the handler routed validation_error responses to inline field rendering and returned no alert. Inline errors are matched to inputs by attr (via getBackendErrorDetail), but non_field_errors maps to no input — so those errors were rendered nowhere and no toast fired. The request looked like it silently succeeded. This became user-visible after the DRF 3.15 bump: duplicate-create errors arrive as nested non_field_errors ("...must make a unique set") instead of a top-level detail. Reproduced on duplicate LLM profile name and table settings save (QA, rc.343).

2. Name the resource in 404s (backend)

DRF returns a bare "Not found." for every 404 across all apps, giving the user no context (the symptom behind the lookup-not-visible toast from unstract-cloud #1570). drf_logging_exc_handler now enriches the 404 detail using the view's queryset model verbose_name, so errors read " not found." app-wide — no per-view or per-caller work, and future endpoints get it automatically.

  • Uses the static queryset attr (not get_queryset()) to avoid running view logic during error handling.
  • Uses model._meta.verbose_name (plain language) rather than the PascalCase class name; models can tune their label via Meta.verbose_name.
  • Views without a static queryset keep the generic message — no regression.

Scope / safety

  • Both changes live in the single central error path → fix every consumer, not one form/view.
  • No new deps. Biome + Prettier + ruff clean.
  • FE change independent of DRF version (valuable on 3.14 today and 3.15 later).
  • BE change covered by backend/middleware/test_exception.py (4 cases: enriched, no-queryset fallback, non-404 untouched, None-safe).

🤖 Generated with Claude Code

When a form passes setBackendErrors, useExceptionHandler routed validation
errors to inline field rendering only and returned no alert. Errors whose
attr is non_field_errors (or has no attr) map to no input field, and
getBackendErrorDetail matches errors by attr, so these were never rendered
anywhere -> the request appeared to silently succeed.

This surfaced after the DRF 3.15 bump, where duplicate-create errors arrive
as nested non_field_errors ("...must make a unique set") instead of a
top-level detail string. Affected user-visible flows include duplicate LLM
profile name and table settings save.

Keep inline field errors as-is; additionally collect any non-field/attr-less
errors and show them in a toast. The toast relays only the backend-provided
detail (not the attr label), so nothing beyond the message itself is exposed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8hAHc4HUo42zY1g9LAjKu
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR updates frontend validation error handling to surface non-field errors as alerts and updates backend DRF exception handling to rewrite 404 error details before logging.

Changes

Frontend validation alerts

Layer / File(s) Summary
Non-field alert handling
frontend/src/hooks/useExceptionHandler.jsx
Filters validation errors for non-field entries, joins their messages with , and returns an alert while leaving field-level errors for inline handling.

Backend 404 detail enrichment

Layer / File(s) Summary
404 detail enrichment
backend/middleware/exception.py
Calls a new helper before logging and rewrites matching 404 error payload details using the model verbose name when the response contains errors.
Helper tests
backend/middleware/test_exception.py
Adds unit tests for 404 enrichment, missing queryset behavior, non-404 no-op behavior, and null response handling.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the core change, but it omits several required template sections like migrations, testing, checklist, and related issues. Add the missing template sections (migrations, env config, docs, related issues, dependencies, testing, screenshots, checklist) and explicitly fill the breakage section.
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the two main changes: surfacing non_field_errors and enriching 404 messages.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fe-non-field-errors

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/hooks/useExceptionHandler.jsx`:
- Around line 54-56: The filter operation on `errors` assumes it is an array,
but it could be a truthy non-array object at runtime, which would cause
`.filter()` to throw an error. Before calling `.filter()` on the `errors`
variable in the nonFieldErrors assignment, add a guard to ensure `errors` is
actually an array. Check if `errors` is an array using Array.isArray(), and if
not, default to an empty array or the normalized array form before applying the
filter logic that checks for missing or "non_field_errors" attr values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 803a2bea-8387-48ff-9c38-06ab1dd87252

📥 Commits

Reviewing files that changed from the base of the PR and between de49f06 and bde1a5a.

📒 Files selected for processing (1)
  • frontend/src/hooks/useExceptionHandler.jsx

Comment thread frontend/src/hooks/useExceptionHandler.jsx Outdated
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two complementary gaps in the central DRF error path: non-field validation errors (e.g. unique-constraint duplicates surfaced as non_field_errors after the DRF 3.15 bump) now produce a visible toast instead of silently disappearing, and 404 responses now carry the resource's human-readable name instead of the generic "Not found." message.

  • Frontend (useExceptionHandler.jsx): When a form provides setBackendErrors, the handler now filters for errors with no attr or attr === "non_field_errors", calls setBackendErrors for inline field rendering as before, and also fires a toast for the non-field errors that would otherwise vanish.
  • Backend (exception.py): A new _enrich_not_found_detail helper runs after exception_handler; it reads the view's static queryset.model._meta.verbose_name and rewrites not_found-coded errors with the resource name. Views without a static queryset keep the generic message. Four unit tests cover the main paths.

Confidence Score: 5/5

Safe to merge — both changes are additive, gated by None-checks throughout, and the backend mutation happens before the response is rendered or returned.

The frontend fix correctly guards with Array.isArray before filtering and uses the " • " separator consistent with the rest of the file. The backend helper is pure in-place enrichment behind multiple None-safe getattr calls, called only on the already-constructed response before logging. Four new unit tests cover the critical paths.

No files require special attention.

Important Files Changed

Filename Overview
frontend/src/hooks/useExceptionHandler.jsx Adds non-field error surfacing via toast when setBackendErrors is provided; Array.isArray guard and " • " separator are in place, logic is correct.
backend/middleware/exception.py New _enrich_not_found_detail helper safely enriches 404 detail using model verbose_name; uses static queryset attr to avoid side effects, with None-safe getattr chaining throughout.
backend/middleware/test_exception.py New unit test file covering 5 cases (enriched, fallback, non-404 untouched, None-safe, non-dict error item); uses minimal Django settings stub so it runs standalone.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant FE as useExceptionHandler
    participant BE as drf_logging_exc_handler
    participant EH as DRF exception_handler
    participant Enrich as _enrich_not_found_detail

    Client->>BE: HTTP Request raises 404
    BE->>EH: exception_handler(exc, context)
    EH-->>BE: Response with generic "Not found."
    BE->>Enrich: _enrich_not_found_detail(response, context)
    Note over Enrich: Reads view.queryset.model._meta.verbose_name
    Enrich-->>BE: detail rewritten to resource name
    BE-->>Client: "Resource not found." JSON

    Client->>FE: handleException(err, msg, setBackendErrors)
    alt validation_error with setBackendErrors
        FE->>FE: setBackendErrors called for inline field errors
        FE->>FE: filter non_field_errors or null attr
        alt non-field errors present
            FE-->>Client: toast with joined error messages
        else only field errors
            FE-->>Client: silent, inline rendering handles it
        end
    else validation_error without setBackendErrors
        FE-->>Client: toast with formatted error list
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant FE as useExceptionHandler
    participant BE as drf_logging_exc_handler
    participant EH as DRF exception_handler
    participant Enrich as _enrich_not_found_detail

    Client->>BE: HTTP Request raises 404
    BE->>EH: exception_handler(exc, context)
    EH-->>BE: Response with generic "Not found."
    BE->>Enrich: _enrich_not_found_detail(response, context)
    Note over Enrich: Reads view.queryset.model._meta.verbose_name
    Enrich-->>BE: detail rewritten to resource name
    BE-->>Client: "Resource not found." JSON

    Client->>FE: handleException(err, msg, setBackendErrors)
    alt validation_error with setBackendErrors
        FE->>FE: setBackendErrors called for inline field errors
        FE->>FE: filter non_field_errors or null attr
        alt non-field errors present
            FE-->>Client: toast with joined error messages
        else only field errors
            FE-->>Client: silent, inline rendering handles it
        end
    else validation_error without setBackendErrors
        FE-->>Client: toast with formatted error list
    end
Loading

Reviews (8): Last reviewed commit: "Merge branch 'main' into fix/fe-non-fiel..." | Re-trigger Greptile

Comment thread frontend/src/hooks/useExceptionHandler.jsx Outdated
Comment thread frontend/src/hooks/useExceptionHandler.jsx Outdated
chandrasekharan-zipstack and others added 3 commits June 22, 2026 21:00
- Array.isArray guard before filter (avoids throw on non-array error payload)
- Join multiple non-field messages with bullet, matching the other branch

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8hAHc4HUo42zY1g9LAjKu
DRF returns a bare "Not found." for every 404 across all apps, giving
users no context. Enrich the detail in the central exception handler
using the view's queryset model `verbose_name`, so 404s read
"<Resource> not found." app-wide with no per-view work. Models can tune
their label via `Meta.verbose_name`; views without a static `queryset`
keep the generic message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8t7DFHq7Dj655kEywKk3K
@chandrasekharan-zipstack chandrasekharan-zipstack changed the title [FIX] Surface non_field_errors as a toast instead of failing silently [FIX] Surface non_field_errors and name resources in 404s in the central DRF error path Jun 24, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/middleware/exception.py`:
- Around line 75-77: The exception handler in `backend/middleware/exception.py`
assumes `data.get("errors", [])` yields dict-like items, so `err.get("code")`
can crash if an item is not a dict. Update the loop in the exception handling
path to verify each `err` is a mapping before accessing `.get(...)`, and skip or
safely normalize any non-dict items so `not_found` handling in this block cannot
raise while handling an exception.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: af91ddb8-b56c-46ff-93e6-e15709cb6b2d

📥 Commits

Reviewing files that changed from the base of the PR and between 1b654ed and e6613ad.

📒 Files selected for processing (2)
  • backend/middleware/exception.py
  • backend/middleware/test_exception.py

Comment thread backend/middleware/exception.py
@github-actions

Copy link
Copy Markdown
Contributor

Frontend Lint Report (Biome)

All checks passed! No linting or formatting issues found.

@github-actions

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
unit-connectors unit 64 12 0 3 17.0
unit-core unit 0 0 4 0 1.2
unit-platform-service unit 9 0 1 0 1.4
unit-prompt-service unit 15 0 0 0 20.8
unit-rig unit 53 0 0 0 3.4
unit-runner unit 11 0 0 0 3.2
unit-sdk1 unit 390 0 0 0 20.9
unit-tool-registry unit 0 0 1 0 1.3
unit-workers unit 0 0 0 0 18.3
TOTAL 542 12 6 3 87.3

Critical paths

⚠️ Critical paths not yet covered

  • auth-login — User can log in and obtain a session cookie. (entry: POST /api/v1/auth/login; declared coverage: no groups declared)
  • adapter-register-llm — Register and validate an LLM adapter. (entry: POST /api/v1/adapter/; declared coverage: no groups declared)
  • workflow-create-execute — Create a workflow, configure source+destination, execute, poll, fetch result. (entry: POST /api/v1/workflow/{id}/execute/; declared coverage: e2e-workflow)
  • api-deployment-run — Deploy a workflow as an API, POST a document, receive structured JSON. (entry: POST /deployment/api/{org}/{name}/; declared coverage: e2e-api-deployment)
  • prompt-studio-fetch-response — Prompt Studio: create project, add prompt, run single-pass, get response. (entry: POST /api/v1/prompt-studio/prompt-studio-tool/{id}/fetch_response/; declared coverage: e2e-prompt-studio)
  • pipeline-etl-execute — Run an ETL pipeline from source connector to destination. (entry: POST /api/v1/pipeline/{id}/execute/; declared coverage: no groups declared)
  • usage-token-tracking — Per-execution token usage is recorded and retrievable. (entry: GET /api/v1/usage/get_token_usage/; declared coverage: no groups declared)
  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (entry: internal: backend → rabbitmq → workers/file_processing; declared coverage: no groups declared)
  • callback-result-delivery — Async results are posted back via the callback worker. (entry: internal: workers/callback → backend /internal endpoints; declared coverage: no groups declared)
✅ Covered critical paths
  • tool-sandbox-exec — covered by unit-runner

@chandrasekharan-zipstack chandrasekharan-zipstack merged commit 6c690af into main Jun 24, 2026
10 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the fix/fe-non-field-errors branch June 24, 2026 07:07
@sonarqubecloud

Copy link
Copy Markdown

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.

3 participants