Repository navigation
Which file mode should the daemon leave on a user's own config file? #156
Description
Summary
The daemon writes into files it owns completely (managed settings, the Codex/Grok/OpenCode configs with its header, the Claude Desktop helper script) and into files that belong to the user (Claude Code ~/.claude/settings.json in --user mode, VS Code settings.json, chatLanguageModels.json, Copilot CLI providers.json). For the second group the mode handling grew per feature, and it is not consistent today. I'd like to agree on the intended behaviour before changing anything.
Behaviour on main (ff46fc4, Unix)
| File | Written with | On identical bytes at a looser mode | Removal of our entries |
|---|---|---|---|
| Daemon-owned files (managed settings, Codex/Grok/OpenCode configs, helper script 0755) | the daemon's mode (0644/0755) | rewritten at that mode (#146) | n/a |
Claude Code user settings.json (merge) |
always 0644 | rewritten at 0644 (#146) | keeps the mode it finds |
VS Code settings.json, chatLanguageModels.json, Copilot CLI providers.json (carry the pairing value) |
0600 | rewritten at 0600 | keeps the mode it finds |
Two consequences for the Claude Code user file:
- A user who keeps
~/.claude/settings.jsonat 0600 gets 0644 after the next merge that changes the file (json_merge::plan_merge_withwritesoptions.mode; the 0644 predates feat(agent): manage Copilot CLI and VS Code Copilot Chat #135, which moved it intoMergeOptions::default()). - Since feat: per-program status, opt-in reconcile tick, llmGateway.whenProxyUnavailable #146 a 0664 file is also rewritten at 0644 when nothing else changed. The README says so ("a Claude Code user
settings.jsonat 0664 becomes 0644"). The looser-mode check was added for the pairing files and reaches this file through the shared merge code.
Every write goes through the umask (secure_fs::atomic_write creates the temporary file with the planned mode), so removal "keeps the mode it finds" as far as the umask allows: a 0664 file comes back as 0644 under umask 022. That should stay: the umask is the user's ceiling, and the daemon should not write past it. On Windows the mode argument is ignored and the file gets its folder's ACL.
What the daemon writes into the user files
Nothing confidential goes into the Claude Code user settings (the apiKeyHelper command name, the gateway URL, hooks, announcements; the credential is fetched at run time). The pairing value is a secret: the files that carry it need 0600, since the proxy on 127.0.0.1 is reachable by every local account. The daemon-owned files hold commands that tools execute (hooks, apiKeyHelper, the helper script, MCP commands), so write access by others is the concern there, not read access.
Proposal
Split by who owns the file:
- Daemon-owned files: unchanged (the daemon's mode, drift repaired).
- User files carrying the pairing value: unchanged (0600, a looser file tightened).
- User files without a secret (today only the Claude Code user settings merge): a rewrite keeps the file's mode, through the umask like every write; a file the merge creates gets 0644 through the umask. No tightening of an unchanged file.
An alternative is "never widen": owner read/write, group and other bits at most what both the file and the planned mode allow (0600 stays 0600, 0664 becomes 0644). It is stricter than (3) but still changes a mode the user chose.
Questions
- Is "keep the user's mode" (3) the behaviour we want for user files without a secret, or "never widen", or the current 0644?
- Windows: is the folder ACL enough for the user files, or should a rewrite keep a file's own ACL (
ReplaceFileW)?
Happy to send the change once we agree on the rule.
Activity