Skip to content

fix(errors): serve a 500 page when the disk can't supply one - #3396

Merged
vpetersson merged 1 commit into
masterfrom
agent/anthias-claude/d2d2a207b1af
Oct 19, 2026
Merged

vpetersson merged 1 commit into
masterfrom
agent/anthias-claude/d2d2a207b1af

Conversation

@vpetersson-bot

Copy link
Copy Markdown
Contributor

What's wrong today

Django's django.views.defaults.server_error loads 500.html off disk
and catches only TemplateDoesNotExist. On a device whose storage or
file-descriptor budget has already failed — which is the condition that
produced the original 500 — that load raises an OSError of its own.
Nothing catches it: it escapes handle_uncaught_exception, uvicorn drops
the connection, and the operator gets a browser error rather than a page
telling them their card needs attention.

Seen in the field on a Pi 5 that ran out of file descriptors: three
OperationalError: unable to open database file events — the real fault,
error-tracker issue ANTHIAS-63 — buried under twenty-four
OSError: [Errno 24] Too many open files: '.../app/templates/500.html'
raised while rendering the error page for them (ANTHIAS-62). The cascade
is worse than noise: the tracker ranks by event count, so the symptom
outranked its own cause 8:1.

What this does

Registers a handler500 that tries the branded template and, when
anything at all goes wrong reading or rendering it, serves a page that
has been in memory since import — no extends chain, no stylesheet link,
no static reference. 500.html extends _error.html, which pulls a
layout and a stylesheet, so the working path is several reads deep and
every one of them is covered.

Logged at warning rather than error: the exception that got us here has
already been reported, and re-reporting its fallout once per request is
what buried the cause. It still lands as a breadcrumb on the next event.

Only the 500 handler is replaced. The 4xx handlers read templates the
same way and would fail the same way, but they are reached by ordinary
requests on healthy devices; this one is reached because something is
already broken, which is what makes a disk read the wrong thing to
depend on here.

How I know it's right

A throwaway parametrized run against both handlers, with
loader.get_template raising EMFILE and a view raising
OperationalError:

django.views.defaults.server_error:    RAISED OSError(24, 'Too many open files') — client gets nothing
anthias_server.lib.error_pages.server_error:  500, 897 bytes

Committed coverage in tests/test_error_pages.py:

  • the healthy path still serves the branded template;
  • the fallback fires for EMFILE, EIO, ENOTCLEAN (117 — the errno
    behind the corrupted-ext4 crash-loop that
    ResilientWhiteNoiseMiddleware exists for) and TemplateSyntaxError,
    and the body it serves contains no <link and no /static/;
  • the fallback logs exactly one record, at WARNING, with traceback;
  • the root URLconf actually resolves handler500 to this view — a
    handler nobody routes to fixes nothing, and a typo there would only
    surface during a real outage;
  • end-to-end through the exception middleware: view raises, template
    read fails, client still gets a 500 with a body.

uv run pytest -m "not integration" — 2376 passed, 3 skipped.
ruff check, ruff format --check and mypy . are clean (the two
tools/image_builder import-not-found errors are pre-existing, from the
optional docker-image-builder dependency group).

🤖 Generated with Claude Code

Django's `django.views.defaults.server_error` loads 500.html off
disk and catches only `TemplateDoesNotExist`. On a device whose
storage or file-descriptor budget has already failed — which is the
condition that produced the original 500 — that load raises an
`OSError` of its own, escapes `handle_uncaught_exception`, and
uvicorn drops the connection. The operator gets a browser error
instead of a page telling them their card needs attention.

Seen in the field on a Pi 5 that ran out of file descriptors: three
`OperationalError: unable to open database file` events (the real
fault, Sentry ANTHIAS-63) buried under twenty-four `OSError: [Errno
24] Too many open files: '.../app/templates/500.html'` raised while
rendering the error page for them (Sentry ANTHIAS-62). The cascade
is worse than noise — the tracker ranks by event count, so the
symptom outranked its own cause 8:1.

Register a `handler500` that tries the branded template and, when
anything at all goes wrong reading or rendering it, serves a page
that has been in memory since import: no extends chain, no
stylesheet link, no static reference. 500.html extends _error.html,
which pulls a layout and a stylesheet, so the working path is
several reads deep and every one of them is covered.

Logged at warning, not error: the exception that got us here has
already been reported, and re-reporting its fallout once per request
is what buried the cause. It still lands as a breadcrumb.

Only the 500 handler is replaced. The 4xx handlers read templates
the same way, but they are reached by ordinary requests on healthy
devices; this one is reached because something is already broken,
which is what makes a disk read the wrong thing to depend on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
@vpetersson-bot
vpetersson-bot requested a review from a team as a code owner October 18, 2026 10:14
Copilot AI lite review requested due to automatic review settings October 18, 2026 10:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

@vpetersson-bot
vpetersson-bot force-pushed the agent/anthias-claude/d2d2a207b1af branch from 8c931e2 to 94504f7 Compare October 19, 2026 08:53
@sonarqubecloud

Copy link
Copy Markdown

@vpetersson
vpetersson merged commit f898735 into master Oct 19, 2026
9 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.

3 participants