feat(ai): global command-approval allowlist for Claude Code and Antigravity#716
Merged
Conversation
…ravity Defines safe-command families once in .chezmoidata/ai/command_policy.toml (git-local, beads) and renders them into each tool's native permission syntax, so approvals no longer have to be re-accumulated per project. Overlays can extend/shrink a family via add/remove, or add sibling families, without restating chezmoi's list-replace-on-collision data. Also drops tools/files under claude_code.toml's permissions block — neither exists in Claude Code's documented settings schema, verified against the permissions reference; they were inert.
One file per family under .chezmoidata/ai/command_policy/ (git.toml, beads.toml) instead of one combined file — matches the existing .chezmoidata/bin/ per-tool catalog pattern, so adding a family later is a new file, not an edit. Documents the schema/overlay mechanism in AGENTS.md next to the analogous BOM section rather than repeating it per data file.
Renderers already default enabled to true when absent (dig "enabled" true $family), so declaring it in git.toml/beads.toml was a no-op. Only an overlay disabling a family needs to write enabled = false.
4 tasks
mkobit
added a commit
that referenced
this pull request
Jul 15, 2026
…717) * refactor(ai): flatten command_policy to a command->bool dict Families + prefixes + add/remove/enabled all existed to work around chezmoi replacing list values wholesale on collision. Storing entries as dict keys instead removes the problem at the root: an overlay adds a command by adding a key, or removes one by setting it false — plain dict merge, same pattern .chezmoidata/ai/sandbox.toml already uses. Renders byte-identical output to the schema this replaces (verified against the live ~/.claude/settings.json and ~/.gemini/antigravity-cli/settings.json applied in PR #716) — pure schema simplification, no behavior change. * feat(ai): allow/ask/deny tri-state for command_policy commands Replaces the true/false value with a string mode matching both tools' real permission tiers (Claude Code has permissions.allow/.deny/.ask; Antigravity's docs state "Deny > Ask > Allow" precedence). "ask" (or an unset command) renders nowhere in either tool -- absence from allow/deny is what falls back to a normal per-use prompt. All current commands stay "allow" -- no behavior change, verified by re-rendering against the live settings files with zero diff. deny rendering is wired but unexercised (no command uses it yet); the Antigravity deny(...) key name is inferred from documented precedence language, not yet live-tested, flagged as a comment in the template. * docs(ai): move command_policy detail to src/chezmoi/AGENTS.md Root AGENTS.md keeps a 1-2 sentence summary + link; full schema/ rendering/overlay detail moves to src/chezmoi/AGENTS.md, next to the Notable cross-cutting features bullet (matches the existing sandboxr pattern). Not placed inside .chezmoidata/ai/command_policy/ itself -- tried that first and it breaks every template reading chezmoi data (chezmoi parses every file under .chezmoidata/ as a data source; a .md file there errors with "unknown format"). * docs(ai): use markdown links instead of backtick paths for cross-references * fix(ai): antigravity permissions fully replaced, not union-merged User preference: chezmoi should own permissions.allow/.deny outright rather than preserving whatever agy's own "always allow" flow wrote into the live file -- matches Claude's existing full-replace behavior. Also fixes a real bug this surfaced: $agPermissions (built to carry an optional "deny" key) was computed but never wired into the actual $settings dict, which still built permissions inline with only "allow" -- deny was dead code for this tool, unverified until now. Confirmed working with a throwaway test entry (not committed). * refactor(ai): convert both settings.json modify scripts to pure chezmoi:modify-template Drops the embedded-Python round-trip entirely for both dot_claude/modify_settings.json and dot_gemini/antigravity-cli/modify_settings.json, matching the existing dot_codex/modify_config.toml precedent (fromJson .chezmoi.stdin -> template logic -> toPrettyJson). Requires dropping both the .tmpl suffix and the executable bit -- chezmoi's chezmoi:modify-template dispatch doesn't recognize the file otherwise (confirmed empirically; the executable-script code path silently takes over instead and .chezmoi.stdin never gets populated). toPrettyJson's return value always carries a trailing "\n" that trim markers can't remove (they only affect surrounding template text, not a pipeline's runtime value) -- piped through trimSuffix "\n" to match the no-trailing-newline convention both live files already use. No behavior change: both verified via chezmoi diff against the live settings files (zero diff), including the deny bucket (tested with a throwaway, uncommitted data file, both tools). * fix(claude): move editorMode to the documented settings.json location editorMode is a documented top-level settings.json key (code.claude.com/docs/en/settings); ~/.claude.json is documented as OAuth session / MCP config / per-project trust state / caches, not a settings store. The current claude_code.preferences -> modify_dot_claude.json.tmpl injection (PR #718) targets the wrong file, same category of bug as the tools/files no-op keys already removed from this same settings block. Confirmed settings.json is the real, effective location before adding this: the live ~/.claude/settings.json already had editorMode = "vim" (from toggling /config previously, not from any chezmoi-managed source) -- Claude Code itself reads/writes it there. Also fixed a stale comment referencing the old ai.command_policy.families / single-file .chezmoidata/ai/command_policy.toml shape, superseded by this branch's flat commands dict under .chezmoidata/ai/command_policy/*.toml. Not removing the ~/.claude.json injection yet -- that file is modified on the separate open PR #718 branch; removing claude_code.preferences here risks a merge-order-dependent break of that script. Left as an explicit follow-up for after both PRs merge. * fix(claude): stop managing ~/.claude.json entirely, delete the dead injection editorMode was the only key claude_code.preferences ever injected into ~/.claude.json via modify_dot_claude.json.tmpl. Now that it lives in claude_code.settings (-> ~/.claude/settings.json, the documented location, previous commit), that injection is a pure no-op -- and ~/.claude.json itself is documented as OAuth session / MCP config / trust state / caches, not a place a dotfiles repo should be templating content into at all. Deletes modify_dot_claude.json.tmpl and claude_code.preferences outright rather than leaving inert infrastructure around. This also makes moot a separate bug found while investigating: the file had lost its executable bit (required for chezmoi's legacy external- script modify_ mechanism) and chezmoi diff was rendering its raw unexecuted Python source as the literal target content -- would have replaced the live ~2000-line ~/.claude.json with ~46 lines of garbage had it ever been applied in that state. Deleting the file removes the bug's entire surface rather than patching a mechanism with no reason to exist anymore. Updates dot_claude/AGENTS.md (single managed file now, not two) and the stale modify_dot_claude.json.tmpl cross-reference in dot_claude/modify_settings.json. Supersedes PR #718, which was fixing formatting on the now-deleted file -- closing that PR instead of merging it. * docs: default modify_ scripts to chezmoi:modify-template Ported from PR #718 (being closed, superseded -- the file it fixed, modify_dot_claude.json.tmpl, was deleted outright in the previous commit rather than patched). Documents the two sharp edges found empirically (no .tmpl suffix, no executable bit -- both break chezmoi's dispatch silently) and the trimSuffix "\n" fix for toPrettyJson's baked-in trailing newline, plus the general key- ordering caveat for files another live process also writes to. * docs: reformat AGENTS.md prose to one sentence per line
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.chezmoidata/ai/command_policy.toml: one tool-neutral catalog of named command "families" (git-local,beads), each a flat list of safe command prefixes.Bash(<prefix>:*)for Claude Code,command(<prefix>)for Antigravity — via small additions to the existingmodify_settings.json.tmplscripts for each tool. Mirrors the existingguidelines.toml"one source, many renderers" pattern.add/removeprefix lists, disabled viaenabled = false, or a whole new sibling family can be added — all through normal chezmoi dict-merge, working around chezmoi's list-values-replace-on-collision behavior.chezmoi apply. Claude's side fully owns the array (its own "don't ask again" persists into project-levelsettings.local.json, not the user-level file this manages).tools/fileskeys fromclaude_code.toml'spermissionsblock — verified against Claude Code's permissions reference that neither key exists in the documented schema; they were inert.git-localcovers status/log/diff/show/blame/worktree-list/fetch/pull/add/commit/branch/switch/checkout/restore/stash/rebase/merge/cherry-pick — all local, commit-or-object-level operations, reflog-recoverable. Deliberately excludespush(the actual remote-write/leak risk), andreset/clean(can permanently destroy uncommitted/untracked work with no reflog entry, and prefix-matching can't separate safe flags like--softfrom destructive ones like--hard/-fd).Test plan
modify_settings.json.tmplfiles viachezmoi execute-templateand confirmed valid Python + expected JSON shape.~/.claude/settings.jsonand~/.gemini/antigravity-cli/settings.json— confirmed correct merge output,tools/filesstripped, Antigravity's existing manual grants preserved.add/remove/sibling-family composition mechanism with a temporary throw-away data file (removed after, no trace in this PR).chezmoi diffagainst the live home directory shows only the intendedpermissionsblock changes — no other settings touched.chezmoi applynot yet run for real on this machine — pending after merge.