fix(userconfig): harden global config file management#3529
Merged
Conversation
- fix TUI startup panic when the config file is malformed (unchecked *RestoreTabs deref); add nil-safe GetRestoreTabs - log ignored config load errors instead of silently falling back to defaults, and surface an unparseable config in `doctor` - preserve YAML comments and unknown keys across load/save cycles so hand-edits and newer-version fields survive TUI toggles - add userconfig.Update: file-locked load-mutate-save cycle, and migrate all writers (alias, sandbox, theme, layout, board) to it so concurrent processes no longer overwrite each other - validate user-config global hooks like hook drop-ins - warn on unknown config version; marshal under the config mutex
dgageot
approved these changes
Jul 8, 2026
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.
Summary
A check-up of the global config file management (
~/.config/cagent/config.yaml)surfaced one crash and several silent data-loss paths in the load/save cycle.
Every issue found is fixed here.
Issues and fixes
Get()returnsSettings{RestoreTabs: nil}on load error andtabs.godereferences it unconditionallyGetRestoreTabs()getter (consistent withGetSound()etc.);Get()error path returns normalized defaultsGet(),ResolveAlias,resolve(), and the credential-helper source;docker agent doctorgains a "User configuration" section and reports an unparseable config as an issue (non-zero exit)CommentMap; unknown keys are preserved through,inlinemaps onConfigandSettings(marshal falls back to plain serialization if comment re-attachment fails, so a save never fails because of a comment)docker agent sandbox allowwhile the board is open is wiped by the board's next saveuserconfig.Update(mutate)serializes load-mutate-save behind an advisory file lock (flock on unix,LockFileExon windows, 5s timeout, best effort); all writers migrated: alias add/rm, sandbox allow/deny, theme, layout, split-diff, boardsettings.hooksis consumed without validation, unlike hook drop-insGlobalHooks()validates and skips invalid hooks with a warning; the section stays untouched on diskversion:is stamped on save but never checked on loadSave()marshals without the mutex that guardsAliasessaveToandGetSettingstake the config mutexWrite path after the change
The board now refreshes its cached config from disk on every save instead of
rewriting a snapshot held since startup, so changes made by other processes to
non-board sections survive a board session.
Behavior notes
sandbox denyof an absent host no longer rewrites the config file.Get, alias resolution, completion) take no lock: theatomic write already guarantees a consistent view.
logs a warning, matching the previous behavior rather than failing the save.
Tests
Get()returns safe defaults, no panicUpdatewriters lose no changes (also run with-race)doctorreports an unparseable user config and exits non-zeroPre-existing SSRF/network test failures (
TestURLSource_Read_RejectsLocalAddresses,TestNewSSRFSafeTransport_RefusesPrivateIP, ...) are unrelated: they failidentically on a clean tree in this environment because a local proxy
intercepts the requests those tests expect to be refused.