Skip to content

fix: config.json errors are surfaced, named, and never overwritten - #134

Open
Emasoft wants to merge 4 commits into
mlhher:mainfrom
Emasoft:fix/config-error-surfacing
Open

Emasoft wants to merge 4 commits into
mlhher:mainfrom
Emasoft:fix/config-error-surfacing

Conversation

@Emasoft

@Emasoft Emasoft commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Update

Theme-persistence SaveConfig refusals are no longer silently swallowed (45417ca): both _ = config.SaveConfig(...) sites now surface the refusal on the focused agent's status line as settings changed but won't persist: <reason>. Previously, a degraded config (a config.json that exists but cannot be read or parsed, so LoadConfig returns flagged defaults and SaveConfig refuses to overwrite the hand-edited file) made the chosen theme silently fail to survive a restart. The theme still applies for the session, and healthy configs persist exactly as before, with no status text. Tests added: a degraded config drives both sites and asserts the warning, the refusal reason, and that the broken file survives; a non-degraded config asserts no warning and that the theme really persisted (internal/tui/config_persist_feedback_test.go).

Honest scope correction: this branch has no /infobar or /timestamps (those live in other PRs) — the two persistence sites here are the theme picker and /themes.

Any config.json parse error silently swapped the user's config for the defaults — and a later runtime toggle (theme, model pick, anything that persists) would then write those defaults straight over the hand-edited file. This makes the failure visible and the file safe.

  • LoadConfig wraps every read/parse error with the exact config path and sets a Degraded flag on the returned defaults, so "fresh install" is distinguishable from "your config is broken".
  • SaveConfig refuses to persist a degraded config — defaults can never overwrite the hand-edited file.
  • The TUI surfaces the error at startup: the bootstrap status shows config error: <path>: <cause>.
  • The model picker shows the OS-real config path (~/Library/Application Support/late on macOS) instead of a hardcoded Unix path.

Small and self-contained: no behavior change for valid configs.

Split out of #133 so each change can be evaluated separately (per review).

Testing

$ go test ./... -race -count=1
ok  	late/cmd/late	3.595s
?  	late/cmd/mcp-run	[no test files]
ok  	late/internal/agent	3.106s
?  	late/internal/assets	[no test files]
ok  	late/internal/client	1.763s
ok  	late/internal/common	5.298s
ok  	late/internal/config	2.485s
ok  	late/internal/executor	9.238s
ok  	late/internal/git	2.843s
ok  	late/internal/mcp	2.194s
ok  	late/internal/orchestrator	4.370s
?  	late/internal/pathutil	[no test files]
ok  	late/internal/plugin	15.341s
ok  	late/internal/session	4.681s
ok  	late/internal/skill	1.888s
ok  	late/internal/tool	5.167s
ok  	late/internal/tool/ast	4.449s
ok  	late/internal/tui	10.649s

exit 0 — all 18 packages green (15 ok, 3 without test files).

  • gofmt -l on the touched files: no output (clean)
  • go vet ./cmd/late ./internal/config ./internal/tui: clean
  • New tests: degraded load + save-refusal (internal/config/config_test.go), bootstrap config error: warning (cmd/late/bootstrap_test.go), model-picker real path (internal/tui/model_picker_test.go)

LoadConfig wraps parse/read errors with the exact config.json path and
flags the returned fallback as Degraded; SaveConfig refuses to persist a
degraded config so runtime toggles can no longer replace the user's
hand-edited file with defaults.
A config.json that fails to load now shows 'config error: <path>: <cause>'
in the status bar at startup instead of vanishing into stderr.
The 'No models configured' empty state hardcoded ~/.config/late, which is
wrong on macOS; the path now comes from pathutil.LateConfigDir().
@mlhher

mlhher commented Sep 24, 2026

Copy link
Copy Markdown
Owner

@Emasoft One change needed: From reading the code the warning is presumably instantly replaced by the runBootstrap message likely causing some kind of flicker. Please make sure users have a chance to read it. Will be merged afterwards.

The two silent _ = config.SaveConfig(...) calls — the theme picker's
enter path and the /themes <name> inline path — swallowed the
degraded-refusal error: when config.json existed but could not be read
or parsed, LoadConfig marks the fallback config Degraded and SaveConfig
refuses to overwrite the user's hand-edited file, so the chosen theme
silently failed to survive a restart.

Surface it: when SaveConfig returns an error the focused agent's status
line shows "settings changed but won't persist: <err>"; the theme still
applies for the session (its toast is unchanged), and healthy configs
persist exactly as before with no status text. The model picker's save
site already reported failures through m.Err and is untouched.

Tests: a degraded config constructed via config.LoadConfig over a
malformed config.json (and one flagged Degraded directly) drives both
sites and asserts the warning, SaveConfig's refusal reason, and that
the broken file survives; a non-degraded config asserts no warning and
that the theme really persisted.
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.

2 participants