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:
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).
Summary
infrastructure/scripts/generate-settings-json.jsemits aWrite(path)rule alongside everyEdit(path)rule it writes into.claude/settings.json. Claude Code does not evaluateWrite(path)in its file permission checks — onlyEdit(path)rules are matched, and anEdit(path)rule already covers every file-editing tool (Write, Edit, NotebookEdit).The result is that half of the generated
permissionsblock 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:
In our project that is 51 warning lines (47 from
deny, 4 fromallow) before any work starts.Source
infrastructure/scripts/generate-settings-json.jsused twice in
generatePermissions():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 itsEdit(...)twin. We verified this before pruning our own file: all 47 deny paths and all 4 allow paths that carried aWrite(...)rule are covered by the matchingEdit(...)rule; none was left uncovered.The cost is ergonomic, not protective:
permissionsblock is roughly twice its necessary size (in our project:deny102 → 55 rules,allow19 → 15, after removing theWrite(...)entries)Suggested fix
Drop
'Write'from the tool list: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) thatEdit(path)is the form that governs all file writes, since the two-tool version looks deliberate to a reader.Environment
@aiox-squads/core-internal5.4.1 (also present in 5.3.0 — the file predates our update)node .aiox-core/infrastructure/scripts/generate-settings-json.js "$PWD"in a project withboundary.frameworkProtection: true, then starting a Claude Code sessionWorkaround in use
We prune the
Write(...)lines from.claude/settings.jsonafter every run of the generator (kept as an explicit step in our framework-update ritual, since the next run re-adds them).