Skip to content

feat(grokcli): import the verbose [permission] rules form - #2642

Merged
dyoshikawa merged 3 commits into
mainfrom
resolve-issue-2498-grok-rules-import
Aug 8, 2026
Merged

feat(grokcli): import the verbose [permission] rules form#2642
dyoshikawa merged 3 commits into
mainfrom
resolve-issue-2498-grok-rules-import

Conversation

@dyoshikawa

Copy link
Copy Markdown
Owner

Part of #2498 (the [permission] rules import slice from the 2026-08-06 comment)

Problem

Grok's settings reference documents two permission forms under [permission]: the compact allow/deny/ask arrays of Claude-style entries, and the verbose rules = [{ action = "allow", tool = "bash", pattern = "git *" }] tables.

parseGrokPermissionArrays in src/features/permissions/grokcli-permissions.ts read only the three string arrays and returned null when all of them were empty or absent. A config written entirely in the verbose form therefore imported to empty canonical permissions and fell back to the coarse [ui] permission_mode; the rules array survived only as a preserved foreign key.

Change

toRulesyncPermissions now also walks [[permission.rules]]:

  • The entries are TOML tables, not entry strings, so a dedicated parseGrokRule maps the tool + pattern fields instead of calling parseGrokEntry on a table. MCPTool folds its address into the canonical category exactly as the compact form does, so both forms produce the same mcp__<address> shape.
  • The tool field is matched case-insensitively against the existing tool table. The settings reference writes the verbose form lowercase (tool = "bash") while the compact entries are capitalized (Bash(git *)), so accepting both spellings is what makes the documented example actually import.
  • A rule with no pattern applies to the whole tool (canonical *). Malformed entries (non-table, unknown action, non-string tool) and tools with no canonical equivalent are skipped, matching how unparseable compact entries are already handled.
  • Both forms merge into one result map with an explicit ACTION_RANK comparison (deny > ask > allow) rather than relying on apply order, so precedence holds across sources.
  • A non-empty rules array counts as "fine-grained rules present", so the coarse permission_mode fallback is not taken even when every individual rule turns out to be unsupported.

Not resolved here

Export stays on the compact array form. Generate-side coexistence with a user's untouched rules array is not resolved by this PR: the array is still preserved verbatim and is not reconciled against the arrays rulesync writes. The class docstring states this explicitly.

#2498 stays open — its sandbox.toml deny-glob ignore-adapter gap is untouched.

Tests

  • src/features/permissions/grokcli-permissions.test.ts: verbose-only config imports to canonical (with always-approve present, proving the fallback is skipped); mixed rules + arrays resolve strictest-wins; missing pattern becomes *; lowercase and capitalized tool names both parse; malformed/unsupported rules are skipped without triggering the fallback.
  • src/e2e/e2e-permissions.spec.ts: a new grokcli import case driving the verbose form end to end.

Full pnpm cicheck passes; the e2e permissions spec was run explicitly.

🤖 Generated with Claude Code

cm-dyoshikawa and others added 3 commits August 8, 2026 13:14
Grok's settings reference documents two permission forms under [permission]:
the compact allow/deny/ask arrays of Claude-style entries and the verbose
rules = [{ action, tool, pattern }] tables. The adapter parsed only the
arrays, so a config written entirely in the verbose form imported to empty
canonical permissions and fell back to the coarse [ui] permission_mode.

toRulesyncPermissions now also walks [[permission.rules]]. The entries are
TOML tables rather than entry strings, so the tool field is resolved through
a case-insensitive view of the same tool table (the reference writes it
lowercase; the array entries are capitalized). Rules from both forms merge
into one map with an explicit deny > ask > allow ranking, and a non-empty
rules array counts as fine-grained rules present so the coarse fallback is
not taken.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The settings reference restricts the verbose rule's tool field to
any | bash | edit | read | grep | mcp | webfetch, so MCP is spelled 'mcp'
there rather than the compact form's 'MCPTool'. Only the compact spelling
was matched, which dropped every documented MCP rule on import. Both
spellings are now accepted.

Also guard the tool lookup with Object.hasOwn so a tool named after an
Object.prototype member cannot resolve to a bogus category, reuse
PermissionActionSchema instead of a hand-written action guard, and bring
the Grok section of docs/reference/file-formats.md in line with the new
import behaviour.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regenerate src/generated/docs-content.ts over the merged docs, and clarify
in the class docstring that the verbose tool lookup also accepts the
documented 'mcp' alias rather than only the shared tool table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dyoshikawa
dyoshikawa merged commit dbdf2fd into main Aug 8, 2026
9 checks passed
@dyoshikawa
dyoshikawa deleted the resolve-issue-2498-grok-rules-import branch August 8, 2026 21:39
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.

2 participants