Humanize the opaque "An unknown error occurred" provider error#322
Merged
dannon merged 1 commit intoJun 19, 2026
Merged
Conversation
f515b9b to
d5fb49d
Compare
When a Gemini turn ends on a finish reason like RECITATION, SAFETY, or a malformed tool call, pi-ai collapses it to a bare "An unknown error occurred" and we echoed that straight into chat (galaxyproject#320) -- no file written, no hint at what to do. The real reason is gone by the time the string reaches the renderer (message_end with stopReason "error"), so detect the sentinel in the humanizer and return something actionable: rephrase and resend, ask for a summary instead of reproducing text verbatim, or switch models. The same sentinel shows up across every pi-ai provider, so the match isn't Gemini-specific. It's caught only in the bare-string branch so a structured error that merely embeds the phrase still gets its typed handling.
d5fb49d to
5ff827a
Compare
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 this does
Asking the agent to "save the full chat history to a Markdown file" (provider google / gemini-2.5-flash) replied with a bogus "...saved as chat_history.md" immediately followed by "An unknown error occurred" -- no file written, nothing in the Files pane (#320). This humanizes that opaque error into something the user can act on.
Root cause
"An unknown error occurred" isn't a Loom string -- it's absent from the repo at every tag (incl.
v0.3.1) and from git history. It's a pi-ai provider sentinel: when a turn ends withstopReason"error"/"aborted", the provider streamsthrow new Error("An unknown error occurred")and discard the real reason. For Gemini,mapStopReasonbuckets RECITATION, SAFETY, MALFORMED_FUNCTION_CALL, OTHER, BLOCKLIST, ... all into"error"(onlySTOP/MAX_TOKENSescape). The sentinel reaches the renderer via themessage_endpath (stopReason === "error"), wherehumanizeAgentErrorpreviously echoed the bare string verbatim. pi-ai is pinned^0.78.0in bothv0.3.1and currentmain, so the path is unchanged -- the bug reproduces today.Asking the model to reproduce the whole conversation verbatim is itself a classic recitation/safety trigger, and there's no agent-callable chat-export tool (the "Export Chat" button is renderer-only), so the model has to reconstruct the transcript -- which is what trips the stop.
The fix
A sentinel case in
error-humanizer.tsthat detects the fixed phrase and returns an actionable message (rephrase/resend, ask for a summary instead of verbatim text, switch models). The exact reason is gone by the time we see it, so the message names the common causes rather than guessing one. The match is caught only in the bare-string branch, so a structured error that merely embeds the phrase still gets its typed handling.What's intentionally NOT here
The issue's second angle -- don't let the agent claim "saved as X" when no write happened -- is a model-narration-without-tool-call behavior with no clean deterministic lever (the prose is already streamed before the turn errors). Left for discussion rather than patched, so this Addresses #320 but doesn't close it. Open questions:
context.tsprompt nudge (write files via a tool and confirm only after success; prefer a summary over reproducing large text verbatim)? Best-effort on weak models, but it would plausibly cut the recitation trigger, not just relabel the failure.exportAsMarkdown) so the model never reconstructs the conversation -- sidesteps both the recitation stop and the false claim. That's a feature, not a bugfix.Review
Ran a Codex adversarial pass. It surfaced two risks:
api_errorwhose message embedded the phrase would be swallowed instead of getting its typed, retriable handling. Moved into the bare-string branch; added a regression test.feedShell's top-levelerrorcase appendsevent.messageraw. It doesn't affect "Save chat history to a Markdown file" fails with "An unknown error occurred" and writes no file #320 (this error arrives viamessage_end, whichfeedShelldoesn't handle), and that feed is a raw/technical view by design -- flagging rather than widening scope.Testing
npm test: 998 passed / 1 skipped ·apptsc --noEmitclean · prettier/eslint clean.