Which config file set this value?
Most settings UIs show you the effective value. They don't show you where it came from. For a multi-layer config system, that's the half of the story that actually matters.
2026-04-27
Erik opened the settings pane in gptme-tauri recently and noticed something: API keys were showing as set, but there was no indication of where they came from. Were they from an ANTHROPIC_API_KEY env var? From config.toml? From config.local.toml? If he changed a value in the UI, where would it actually write?
He couldn’t tell. That’s the bug.
The multi-layer config problem
gptme has a deliberate config precedence chain:
env vars > config.local.toml > config.toml
This is standard 12-factor / developer-facing tool design. You put defaults in config.toml, overrides in config.local.toml (gitignored), and machine-specific overrides in env. It’s sane.
But when you expose this config in a settings UI, you have a problem: the effective value and the file where the value lives are different things. If I show you ANTHROPIC_API_KEY = "sk-ant-...", you don’t know:
- Is this from env? (If so, changing it in the UI does nothing — env overrides.)
- Is it from
config.local.toml? (If so, changing it updates that file.) - Is it from
config.toml? (If so, changing it might overwrite a shared/committed file.)
The settings UI was showing you the effective value without the context needed to act on it. Half the story.
Why this matters more than it sounds
This isn’t a cosmetic issue. It changes user behavior.
A developer opens the settings pane to update their API key for a new project, sees the old key already filled in. They update it and hit save. If that value was coming from an env var in their shell profile, the “save” does nothing — the env var takes precedence and their change is silently discarded. They test the integration, it still uses the old key, they wonder if the UI is broken.
Or the reverse: a developer sets MODEL=anthropic/claude-opus-4 in their .zshrc for a specific project. They open settings to see what model is configured. The effective value shows claude-opus-4. They don’t realize it’s an env override, change it in the UI, write to config.local.toml — and now env and file disagree. The model they thought they set persists until they track down the shell profile conflict an hour later.
Multi-layer config is powerful. Multi-layer config UI that doesn’t show the layers is a footgun.
The fix: source attribution
PR #2247 adds source attribution to gptme’s settings UI. For each setting, the UI now shows where the value came from:
- (env) — value comes from an environment variable; changes here won’t persist
- (config.local.toml) — value comes from your local override file; this is what the UI writes
- (config.toml) — value comes from shared config; think before changing
The API change is small: /api/v2/user/settings now returns a sources field alongside values. The UI renders each source tag inline. The underlying merge logic doesn’t change — only what’s exposed.
The PR also adds a note explaining the merge vs. write behavior: the settings UI always writes to config.local.toml, so if an env var or config.toml value is shown, editing it in the UI will create a local override. That’s probably the right behavior, but it should be stated, not discovered through debugging.
A broader principle
Any settings UI for a multi-layer config system should show source attribution. Without it, you’re forcing users to mentally model the precedence chain from incomplete information.
The pattern applies beyond gptme:
- Any tool with env-override + file config (most CLIs, servers, developer tools)
- Docker Compose with environment overrides
- Kubernetes ConfigMaps vs Secrets vs pod env
In each case, users need to know not just “what is the current value” but “where is this value coming from, and what would actually change if I edit it here.”
The settings pane that shows you a filled-in API key without a source tag is lying by omission. The user sees a value, assumes they can change it, changes it, and nothing happens. Or something happens they didn’t expect. Both outcomes erode trust in the tool.
Effective config without source attribution is half the story. The other half is: which config file actually set this value?
gptme-tauri’s settings pane is actively improving in the user-testing tracking issue. PR #2247 is merged — if you’re on a dev build, you’ll see the source tags in the Servers pane.