Skip to content

feat(permissions): keep defaultMode in config.json, not a second settings file - #824

Merged
agentforce314 merged 1 commit into
mainfrom
feat/permissions-in-config-json
Aug 9, 2026
Merged

feat(permissions): keep defaultMode in config.json, not a second settings file#824
agentforce314 merged 1 commit into
mainfrom
feat/permissions-in-config-json

Conversation

@agentforce314

Copy link
Copy Markdown
Owner

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.defaultMode was 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.jsonsettings.permissions is read as a dict for allowBypassPermissionsMode and disableBypassPermissionsMode (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

  • 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 — that is what makes this a migration rather than a second store.
  • permissions doubles as a flat rule LIST, so a mode write starts a dict rather than clobbering rules, and a list reads as "no mode configured".
  • 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.

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.json for 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

…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>
@agentforce314
agentforce314 merged commit 1e7ec4a into main Aug 9, 2026
3 checks passed
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.

1 participant