Skip to content

generate-settings-json emits inert Write(path) rules — Claude Code only honors Edit(path), which already covers Write #836

Description

@CalzavaraSouEu

Summary

infrastructure/scripts/generate-settings-json.js emits a Write(path) rule alongside every Edit(path) rule it writes into .claude/settings.json. Claude Code does not evaluate Write(path) in its file permission checks — only Edit(path) rules are matched, and an Edit(path) rule already covers every file-editing tool (Write, Edit, NotebookEdit).

The result is that half of the generated permissions block is inert, and Claude Code emits one warning per rule at session start.

What the user sees

Every session opens with one line per generated rule:

Permission deny rule (.claude/settings.json): Write(.aiox-core/core/code-intel/**) is not
matched by file permission checks — only Edit(path) rules are. Use
Edit(.aiox-core/core/code-intel/**) instead (Edit rules cover all file-editing tools).

In our project that is 51 warning lines (47 from deny, 4 from allow) before any work starts.

Source

infrastructure/scripts/generate-settings-json.js

const TOOLS = ['Edit', 'Write'];   // line 11

used twice in generatePermissions():

for (const denyPath of denyPaths) {
  for (const tool of TOOLS) {            // line 204
    deny.push(tool + '(' + denyPath + ')');
  }
}
// ...
for (const allowPath of allowPaths) {
  for (const tool of TOOLS) {            // line 213
    allow.push(tool + '(' + allowPath + ')');
  }
}

So each boundary path produces two rules, one of which the host never reads.

Impact

No security impact — the boundary still holds. Because Edit(path) covers Write/Edit/NotebookEdit, every L1/L2 path the generator protects remains protected by its Edit(...) twin. We verified this before pruning our own file: all 47 deny paths and all 4 allow paths that carried a Write(...) rule are covered by the matching Edit(...) rule; none was left uncovered.

The cost is ergonomic, not protective:

  • ~50 warning lines at the top of every session
  • the permissions block is roughly twice its necessary size (in our project: deny 102 → 55 rules, allow 19 → 15, after removing the Write(...) entries)
  • the warnings suggest a misconfiguration where there is none, which is noise that trains users to ignore permission warnings

Suggested fix

Drop 'Write' from the tool list:

const TOOLS = ['Edit'];

Edit(path) is the canonical form and already covers all file-editing tools, so this is a pure removal — no path loses coverage. If the constant no longer earns its name with a single entry, inlining it in the two loops would read fine too.

Worth considering as a follow-up: a note in the generator's output (or in .claude/hooks/README.md) that Edit(path) is the form that governs all file writes, since the two-tool version looks deliberate to a reader.

Environment

  • @aiox-squads/core-internal 5.4.1 (also present in 5.3.0 — the file predates our update)
  • Claude Code on macOS (darwin 22.6.0)
  • Reproduced by running node .aiox-core/infrastructure/scripts/generate-settings-json.js "$PWD" in a project with boundary.frameworkProtection: true, then starting a Claude Code session

Workaround in use

We prune the Write(...) lines from .claude/settings.json after every run of the generator (kept as an explicit step in our framework-update ritual, since the next run re-adds them).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions