Adding an external agent writes merged settings back to one scope, leaking values between `settings.json` and `settings.local.json`
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 475
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 178
Description
Summary
Adding an external agent through the interactive agent-management flow (entire agent add / entire agent) auto-enables the external_agents setting. The write path loaded the merged project+local settings view and serialized the whole merged object into a single file. With project scope selected, that committed a developer's personal overrides into the shared .entire/settings.json. With local scope, it froze inherited project values into .entire/settings.local.json as stale overrides. Fixed by #2192.
Affected path
applyAgentChanges in cmd/entire/cli/setup.go. When any newly added or reinstalled agent is external, it persists external_agents: true:
s, loadErr := LoadEntireSettings(ctx) // merged: local overlaid on project
if loadErr != nil {
s = &EntireSettings{}
}
if !s.ExternalAgents {
s.ExternalAgents = true
if opts.UseLocalSettings {
saveErr = SaveEntireSettingsLocal(ctx, s) // whole merged object into settings.local.json
} else {
saveErr = SaveEntireSettings(ctx, s) // whole merged object into settings.json
}
}
Root cause
LoadEntireSettings overlays .entire/settings.local.json on .entire/settings.json and returns the effective runtime configuration. That merged view is right for reads, but it discards which file supplied each field. Writing the whole struct back to one of those files persists fields that belong to the other scope, plus typed defaults the struct materializes.
The repo already knew about this hazard. setEnabledRaw exists precisely so enable and disable edit only the "enabled" key inside each file's own raw content, and its doc comment spells out the leak scenario. The external-agents path kept the load-merged, save-whole pattern anyway.
Failure modes
-
Local leaks into the committed project file. With project scope selected, every local-only override in the merged view (
log_level,absolute_git_hook_path, personalstrategy_options, a personalcheckpoint_remote) lands in.entire/settings.json. These values are gitignored because they are personal, and the next commit publishes them to every collaborator.redaction.openai_privacy_filter.commandcan be copied too. The OPF trust gate refuses to execute a command from the project file, so this is disclosure rather than code execution, but the developer's private command path still ends up in version control. -
Project values freeze into the local file. With local scope selected, inherited project values are written into
.entire/settings.local.json. The local layer overrides the project layer, so later changes to the project file silently stop applying on that clone. A project-widelog_levelchange never reaches that developer. -
Defaults appear out of nowhere.
enabledhas noomitempty, so the write always emits"enabled": true/falseinto the target file even when neither file set it. -
The write is skipped when the flag lives in the other scope. The
if !s.ExternalAgentsguard checks the merged view. Ifexternal_agents: trueexists only in the developer's local file and they add an external agent with project scope, the project file never gets the key. Other clones don't receive the setting the flow claimed to enable. -
A malformed settings file gets replaced with defaults. On any load error the code substituted
&EntireSettings{}and saved it, wiping the target file's content instead of surfacing the parse error.
Reproduction
- In an Entire-enabled repo, put
{"log_level": "debug"}in.entire/settings.local.jsonand{"strategy_options": {"push": false}}in.entire/settings.json. - Run the interactive agent-management flow, add an external agent, choose project scope.
.entire/settings.jsonnow contains"log_level": "debug"and"enabled", not just"external_agents": true. Selecting local scope instead copies project values into the local file.
Fix
#2192 routes the write through the same raw-JSON mechanism enabled uses. settingsTargetFile resolves the selected scope, the matching LoadProjectRaw or LoadLocalRaw reads only that file as map[string]json.RawMessage, only the "external_agents" key is set, and the matching scoped writer saves it back. Unrelated and unknown keys survive, and the other scope is never touched. setEnabledRaw was generalized to setSettingsBoolRaw and shared by both flags. Load errors now propagate instead of being replaced with defaults.
The regression test TestManageAgents_ExternalAgentSettingDoesNotLeakAcrossScopes drives the real interactive flow with a mock external agent and asserts both contamination directions, key preservation, and that exactly one key was added.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The affected entry point is applyAgentChanges in cmd/entire/cli/setup.go, with scoped settings handling and raw JSON helpers described in the issue. Start by reading that flow and TestManageAgents_ExternalAgentSettingDoesNotLeakAcrossScopes; completion is covered by the regression assertions for both scopes, preserved keys, and load-error handling. The issue notes that #2192 already provides the fix.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100