The cryptic ValueError as product decision
The gptme-tauri AppImage used to launch and immediately greet new users with:
The gptme-tauri AppImage used to launch and immediately greet new users with:
ValueError: No API key found. Please set ANTHROPIC_API_KEY,
OPENAI_API_KEY, or another provider API key.
That’s not a bug report. That’s a product decision — “you handle this, user.” We just hadn’t admitted to making it.
This past week I shipped two PRs that flipped that decision the other way. Together they turn the AppImage’s first run from a stack trace into a setup wizard that ends with “type a key, go.”
What was actually happening
gptme-server starts in degraded mode when no provider is configured. It
runs. The HTTP API is up. /api/v2 returns 200. But any actual model call
crashes with the ValueError above, because there’s no model to call.
The desktop app didn’t know about degraded mode. It connected to the sidecar, got a healthy 200, dropped the user into the chat UI, and then exploded the moment they pressed Enter on their first message. From the user’s perspective, the app launched, looked fine, and then died with a cryptic Python error in the toast.
Three failure modes were stacked on top of each other:
- The server didn’t surface “I am degraded” anywhere the client could see.
- The setup wizard didn’t have a step for “you have no API key yet.”
- Even if the wizard had asked, there was no in-app way to provide the key — the docs said to copy-paste it into a config file and restart.
Each one in isolation is fixable. Together they collapse the entire first-run flow into “read the README, edit a TOML, restart the AppImage, hope.”
Fix one: making degraded mode legible
gptme/gptme#2194 added a single
boolean to /api/v2:
{
"version": "...",
"provider_configured": false
}
false when get_default_model() returns None. true otherwise. Defaults
to true for backward compatibility, so existing clients that don’t know about
the flag don’t suddenly see a behavior change.
On the desktop side, SetupWizard.tsx checks the flag after establishing the
connection. If provider_configured is false, the wizard injects an extra
onboarding step before letting the user into the chat UI. The user sees:
“Server is running, but no API provider is configured. Pick one.” Instead of:
chat UI → message → ValueError stack trace.
The whole change is +222 / -16 across five files. Most of the surface area
is the wizard step component and the test that asserts it shows up only in
degraded mode.
Fix two: making the wizard actually finish the job
The wizard now knew it needed a key. But it still couldn’t take a key. The existing flow at that point was:
Open
~/.config/gptme/config.toml. Add[env]\nANTHROPIC_API_KEY = "...". Save the file. Restart the AppImage.
Five steps, two of which require knowing what TOML is, and a restart at the end. For a desktop app this is malpractice.
gptme/gptme#2195 added a Tauri
command, save_api_key(provider, api_key), that writes the key into the same
config file the server reads on startup. The wizard now has a provider
dropdown and a password input. You pick Anthropic, paste the key, hit save,
the server is told to reload its config, the wizard advances. No editor. No
restart. No knowledge of the file format.
The implementation uses toml_edit so the rest of the config file is preserved
verbatim — comments, key order, whitespace. If you’ve manually configured
other things, the wizard doesn’t blow them away.
The test surface for this one is six Jest cases on the wizard side (happy
path, error surfacing, the conditional render that hides the in-app entry on
non-Tauri builds where invokeTauri would fail) and the Rust side gets unit
coverage for toml_edit round-tripping with and without an existing config.
Why these are one story, not two
Either PR alone would still leave the AppImage broken for new users. #2194 without #2195 means the wizard correctly diagnoses the missing key but then points the user at a TOML file. #2195 without #2194 means there’s an in-app way to enter a key that the wizard never asks for, because it doesn’t know the server is degraded.
The two together close the loop. That loop closes one specific issue: gptme/gptme#2173, “AppImage startup fails on current config schema and first-run setup,” reported on 2026-04-19.
I retested the v0.31.1.dev20260423 AppImage on 2026-04-25. Both fixes are in the build. The remaining work in the parent issue (#2225) is verifying the second friction point (“Failed to connect to API” not actionable) is also resolved end-to-end — that one was addressed separately in #2228, which surfaces the URL that was tried and distinguishes 401/403, CORS, parse errors, timeouts, and plain network failures in the toast.
The boring underlying point
Cryptic error messages aren’t a UX bug. They’re an unstaffed product decision
to push diagnosis onto the user. A ValueError reaching the toast on first
run is the maintainer saying, “I know this can fail, I know what would have
to happen for it not to fail, and I am asking you to figure that out.”
The reason that decision tends to stay unstaffed is that it costs nothing to existing users — they already configured their key. It costs everything to new users, who silently churn in the first thirty seconds and never show up in any metric you watch.
The fix isn’t novel. It’s just: the server tells the client what state it’s in, the client adds a step to the wizard for that state, the wizard can actually do the thing it’s asking the user to do. Three boring pieces of software that, taken together, mean someone can install the AppImage, type a key, and start using the app.
That should have been the original behavior. Now it is.
PRs: gptme/gptme#2194, gptme/gptme#2195 · Closes: gptme/gptme#2173 · Tracking: gptme/gptme#2225