feat(permissions): keep defaultMode in config.json, not a second settings file - #824
Merged
Merged
Conversation
…ings file `permissions.defaultMode` was read and written only in the standalone `~/.clawcodex/settings.json`. That file surprised the one person it exists to serve: Full Access is a FLOOR, a persisted mode outranks it, so a value sitting in a file nobody knew about made interactive sessions silently ask for approval with no sign of why. There is already an obvious home. `config.json` -> `settings.permissions` is the source of truth for `allowBypassPermissionsMode` and `disableBypassPermissionsMode` (has_allow_bypass_permissions_mode / is_bypass_permissions_mode_disabled both read it), so the MODE belongs beside its own flags rather than in a separate store. - Reads: config.json first in the user tier, then the legacy settings.json, so an existing choice keeps working untouched. - Writes: config.json only. A stale legacy value is shadowed by the new write, which is what makes this a migration rather than a second store. - `permissions` doubles as a flat rule LIST in the schema, so a mode write starts a dict rather than clobbering rules, and a list reads as "no mode". - Global tier only (`load_global`, not the merged view): a repo-committed `.clawcodex/config.json` must not be able to raise the mode, matching the trust split the repo-scoped settings files already get. Managed policy stays where it is — a root-owned lockdown in /etc/clawcodex cannot live in a file the user can edit — as do the repo tiers, which are per-project and deliberately untrusted for loosening. Co-Authored-By: Claude <noreply@anthropic.com>
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.
Follow-up to the "why is there a separate
~/.clawcodex/settings.json?" question raised while debugging why a desktop session still asked for approval.Why
permissions.defaultModewas read and written only in the standalone~/.clawcodex/settings.json. That file surprised exactly the person it exists to serve: Full Access is a floor, a persisted mode outranks it, so a value sitting in a file nobody knew about made interactive sessions silently ask for approval with no sign of why.The docstrings justified the split by claiming a type collision with
SettingsSchema.permissions(a rule list). But the code already contradicts that:config.json→settings.permissionsis read as a dict forallowBypassPermissionsModeanddisableBypassPermissionsMode(has_allow_bypass_permissions_mode,is_bypass_permissions_mode_disabled). The mode was the odd one out, living apart from its own flags.What changed
config.jsonfirst in the user tier, then the legacysettings.json, so an existing choice keeps working untouched.config.jsononly. A stale legacy value is shadowed by the new write — that is what makes this a migration rather than a second store.permissionsdoubles as a flat rule LIST, so a mode write starts a dict rather than clobbering rules, and a list reads as "no mode configured".load_global, not the merged view): a repo-committed.clawcodex/config.jsonmust not be able to raise the mode, matching the trust split the repo-scoped settings files already get.What deliberately did not move
Managed policy (
/etc/clawcodex) — a root-owned MDM lockdown cannot live in a file the user can edit, or it is not a lockdown. Repo tiers (<repo>/.clawcodex/settings{,.local}.json) — per-project, and deliberately untrusted for loosening, since a hostile repo can simply commit one.Still reading
~/.clawcodex/settings.jsonfor their own concerns, and worth rehoming in follow-ups: hooks (hooks/config_manager.py), status line, MCP approval state, and startup gates.Testing
New
tests/test_permission_default_mode_in_config.py— read from config.json, write to config.json, neighbouring bypass flags preserved, a rule list not clobbered, Full Access left standing when nothing is set, and the exact reported bug (a stored mode outranking the floor) now pinned in the file people can actually find.Full Python suite: 9918 passed, 15 skipped.
🤖 Generated with Claude Code