Skip to content

feat(claudecode): emit permission path rules in the forms Claude Code matches, and make sandbox authorable - #2472

Merged
dyoshikawa merged 2 commits into
mainfrom
resolve-scrap-issue-2397-claudecode-permissions
Jul 29, 2026
Merged

feat(claudecode): emit permission path rules in the forms Claude Code matches, and make sandbox authorable#2472
dyoshikawa merged 2 commits into
mainfrom
resolve-scrap-issue-2397-claudecode-permissions

Conversation

@dyoshikawa

Copy link
Copy Markdown
Owner

Background

Related issue: #2397 (gaps 4 and 6; gaps 1, 2, 3 and 5 landed in #2471)

Two Claude Code permission-surface gaps remained after #2471.

Changes

Warned permission forms (gap 4). The permissions docs state, as of v2.1.210: "The file permission checks match only Edit(path) and Read(path) rules. A Write(path), NotebookEdit(path), or Glob(path) rule is accepted but never matched by those checks, so Claude Code warns at startup for each allow, deny, or ask rule in one of these unmatched forms." Rulesync generated exactly those forms, so a canonical write: {"docs/**": "deny"} produced a rule Claude Code warned about and never applied.

A canonical write or notebookedit rule carrying a pattern is now written as Edit(pattern), and a glob rule as Read(pattern). A rule whose pattern is * is a tool-name rule with no path — the docs say it is unaffected — so it is still written as the bare Write / NotebookEdit / Glob.

The managed-tool set claims the alias only when a category actually has a path rule, and keeps claiming the original name so entries an earlier rulesync wrote in the warned form are replaced rather than left behind. Verified against a settings.json holding the old forms.

Because two categories can now produce one entry, a disagreement between them (edit allowing what write denies) is warned about; both are still written, and Claude Code applies deny first.

sandbox.* (gap 6). The claudecode permissions override gained a sandbox key — a loose passthrough on the same terms as the existing permissions one — merged into the top level of .claude/settings.json and routed back on import. The merge is one level deep, matching the permissions precedent: sibling keys survive, an authored subtree replaces the existing one. #2129 deferred this explicitly; it is a passthrough rather than a canonical model, so nothing about the shared permission block changes.

Round-trip note

A rewritten rule imports back under edit or read rather than the category it was authored in. That is deliberate: the rule Claude Code applies is an Edit/Read rule, and the previous category produced no effect at all. Import stays tolerant of the legacy forms, so an existing settings.json still round-trips. Documented.

Verification

  • pnpm cicheck (full: code + content)
  • npx vitest run --config vitest.e2e.config.ts src/e2e/e2e-permissions.spec.ts
  • Manual generate/import round trips, including migrating a settings.json that holds the warned forms.

Closes #2397

… matches, and make sandbox authorable

Claude Code's file permission checks match only Edit(path) and Read(path); a Write/NotebookEdit/Glob rule with a path is never matched and warns at startup, so a canonical write/notebookedit rule with a pattern is now written as Edit(pattern) and a glob rule as Read(pattern). Tool-name rules with no path are unaffected. The claudecode permissions override also gained the sandbox subtree, merged into the top level of settings.json and round-tripped on import.
…e sandbox

Review follow-ups: rewriting a write/glob rule to Edit/Read no longer claims those tool names wholesale, which was deleting the ignore feature's Read denies and hand-written Edit rules from settings.json. Ownership is now per emitted entry, which also moves a rewritten entry when its action changes. Sandbox merges recursively so setting a flag under network keeps the denied domains beside it.
@dyoshikawa
dyoshikawa merged commit ea20ccc into main Jul 29, 2026
9 checks passed
@dyoshikawa
dyoshikawa deleted the resolve-scrap-issue-2397-claudecode-permissions branch July 29, 2026 05:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants