Show actionable error when runtime settings endpoint returns 404#436
Merged
charneykaye merged 10 commits intoJul 14, 2026
Merged
Conversation
parseErrorText now handles FastAPI's detail field so a 404 no longer
renders as raw JSON like {"detail":"Not Found"} in the error view.
RuntimeSettingsPanel.loadData() intercepts a 404 on /api/runtime/settings
and replaces the opaque HTTP error with a plain-language message telling
the user to update ConversationSimulator, which is the only actionable
fix when the bundled runtime predates this endpoint.
Test added: verifies the 404 case surfaces the update prompt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 404 handling added for #429 replaced the error text but still routed the whole panel into ApiErrorView, so the reporter's actual complaint stood: the Runtime section is nothing but an error box. Two problems with that: - Only the advanced fields are served by /api/runtime/settings. Provider, model, runtime health and last-benchmark all come from /api/models, which works fine on the older build that 404s. Blocking them is needless — the user cannot select a model from Settings at all on the affected build. - ApiErrorView offers a "Try again" action, and retrying a route that does not exist in the bundled runtime 404s forever. loadData() now treats a 404 as "advanced settings unavailable" rather than a load failure: the panel renders, and the advanced toggle is replaced by a notice explaining that this version does not have the feature, that provider and model still work, and linking SETUP_DOCS_URL for how to update — the issue asked for explicit instructions, and "update ConversationSimulator" with no path to an update is not explicit. Non-404 failures still block as before. Tests: cover that a 404 leaves the provider/model controls usable with no alert, renders the notice with the update link, and hides the advanced toggle; plus a regression test that a 500 still surfaces a blocking error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Reviewed code and pushed fixes. |
The `detail` branch added for issue #429 is the code path that turns `{"detail":"Not Found"}` into readable text for every API call, but it shipped with no test. Add coverage for the 404 string case, for a body carrying both `message` and `detail` (message wins), and for FastAPI's 422 shape. FastAPI returns `detail` as a *list* of `{loc, msg, type}` objects for request-validation failures, not a string. That branch was typed but never handled, so a 422 fell through to the raw-JSON fallback and put `[{"type":"greater_than_equal","loc":[...]}]` in front of the user — the same "error with no helpful info" this issue is about. Join the `msg` sentences instead so no FastAPI error reaches the UI as raw JSON. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Reviewed code and pushed fixes. |
The previous commit taught parseErrorText FastAPI's string detail (404) and
validation-list detail (422), but missed the third shape the backend actually
emits: convsim-core registers no HTTPException handler, so a route that raises
HTTPException(detail={...}) passes the dict straight through. sessions.py's
_conflict() does exactly that, so a 409 still reached the UI as raw JSON:
{"detail":{"message":"...","code":"INVALID_TRANSITION","current_state":"Running"}}
That is the same defect issue #429 reports, on a far more reachable path than
the 404 that prompted it. Read message/msg and code out of an object detail, so
the 409 renders as "INVALID_TRANSITION: Cannot start a session that is already
Running".
Also close the hole underneath: when a body parsed as JSON but carried no human
sentence, the function fell back to the raw text — i.e. it dumped the JSON. Any
unrecognised shape reproduced the bug. Structured bodies now fall back to the
status line ("500 Internal Server Error"), which says no less and says it in
words; non-JSON bodies still pass through as the plain text they already are.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Reviewed code and pushed fixes. |
convsim-core does register a RequestValidationError handler (errors.py), so
its 422s come back as { error: { code, message, details } }, not FastAPI's
default { detail: [{ loc, msg, type }] } list. The comment claimed all three
detail shapes reach the client from convsim-core, which would mislead the next
reader about the backend's contract. The list branch stays — it is what any
build without that handler returns — but the comment now says why it is there.
Contributor
Author
|
Reviewed code and pushed fixes. |
Two gaps in the #429 fix: - The "How to update" link in the runtime-settings notice sent the user to the top of the install page, which opens with a fresh install. The update steps (Steam beta branch, or checking out a newer tag from source) live in its "Updates and rollback" section, so deep-link there via a new UPDATE_DOCS_URL. - parseErrorText now returns the status line for any structured body it cannot read a sentence out of, which silently swallowed { error: "disk is full" } — the previous code at least dumped it as raw JSON. Read a bare string `error` before falling back, so no body loses its only words. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Reviewed code and pushed fixes. |
… http-error Two gaps in the #429 fix, both in code this branch introduced. parseErrorText documents that it never returns a raw JSON body, but a JSON scalar slipped past: `null`, a number, or a bare `true` all parse successfully, fall through the object check, and were returned as `text` — putting the literal string "null" in front of the user, which is the same class of leak as the `{"detail":"Not Found"}` that opened the issue. Fall back to the status line instead, as the structured-but-wordless case already does. RuntimeSettingsPanel treats a 404 as "the bundled runtime predates the route" only when the error kind is http-error. That narrowing is load-bearing and was untested: a 404 with an HTML body maps to runtime-unreachable, which means a static server or proxy answered while core was down. Telling that user to update the app would point them at the wrong problem, so it must keep blocking. Add a test that locks the distinction in. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Reviewed code and pushed fixes. |
Contributor
Author
|
Reviewed code, no issues found. |
1 similar comment
Contributor
Author
|
Reviewed code, no issues found. |
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.
What changed
On first run against a runtime build that predates the
/api/runtime/settingsroute, the Settings → Runtime panel was replaced entirely by:Two things were wrong: the error text was raw JSON, and a 404 from one optional endpoint took down the whole panel with no way for the user to act on it.
Fixes
apps/web/src/api/client.ts—parseErrorTextnow understands FastAPI's error shapes in addition to the convsim-core ({ error: { code, message } }) and convsim-api ({ code?, message }) shapes it already handled:{ detail: string }for standard HTTP errors (404, 500, …){ detail: { message, code, … } }for state-conflict errors where convsim-core raisesHTTPException(detail={…})without further reshaping{ detail: [{ loc, msg, type }] }for 422 request-validation failures, whosemsgsentences are joined{ error: string }for bare error strings (not emitted in-tree today, but prevents dropping the only message in a body)This is a general fix — no FastAPI error reaches the UI as raw JSON now, not just this one. The function also never echoes JSON scalars (null, numbers) that parse fine but carry no sentence.
apps/web/src/components/RuntimeSettingsPanel.tsx— a 404 fromgetRuntimeSettings()no longer blocks the panel. Only the advanced fields (context length, GPU layers, CPU threads, sampling) come from that endpoint; provider, model and runtime health come from/api/modelsand still work. So the panel now degrades instead of failing: it keeps the working controls, hides the advanced toggle, and shows a status notice explaining that advanced settings need a newer version, with a link to the update instructions. Any non-404 failure still surfaces as a blocking error, since that is a real fault worth retrying. A 404 that indicates the runtime is unreachable (HTML body instead of JSON) is also still blocking, since it's a different problem than "your app is out of date."apps/web/src/setup/docsUrls.ts— addedUPDATE_DOCS_URLpointing to the "Updates and rollback" section of the install docs (not the top of the page), so the user can find the specific update steps they need.Testing
RuntimeSettingsPanel.test.tsx: on a 404 the panel stays usable, the notice explains the update and links to the docs, and the advanced toggle is gone; a 500 still blocks; a 404 that means the runtime is unreachable (viaruntime-unreachableerror kind) still blocks.client.test.ts: FastAPI 404detailstring, 422detaillist, object-shapeddetailwithmessage/code, bareerrorstring, JSON scalar fallback, and precedence ofmessageoverdetail.pnpm typecheckis clean.Closes #429