permission-allowlist-maintain¶
Kuratiert die eingecheckte .claude/settings.json permissions.allow-Liste gemäß Permission-Allowlist-Spec.
Curates the committed .claude/settings.json permissions.allow list of the current repository per spec/claude/permission-allowlist/. Proposes additions sourced from the fewer-permission-prompts built-in or from the user, applies the spec's three-condition selection criteria (frequent + read-only + not-already-autoallowed), rejects forbidden pattern classes (interpreter wildcards, task-runner wildcards, mutation-capable gh/git wildcards), and routes every change through the standard pull-request flow. Invoke when the user asks to \"tidy the permission allowlist\", \"add Bash(task lint) to the allowlist\", \"review .claude/settings.json\", or equivalent German-language requests. Don't use to edit .claude/settings.local.json or ~/.claude/settings.json (out of scope per spec). Supports resume on re-invocation per spec/claude/resumable-work/.
- Plugin:
nolte-shared - Phase: 3 Design (
design) - Tags:
scaffolding,audit - Quelle: skills/permission-allowlist-maintain/SKILL.md
Anwenden wenn¶
- you want to tidy the .claude/settings.json allowlist
- you want to add a frequent read-only Bash or tool entry to the allowlist
- you want to review an existing allowlist against the spec's three-condition rule
Permission Allowlist Maintain¶
Implements spec/claude/permission-allowlist/ for the repository's committed .claude/settings.json. The skill is the curator: it gathers candidates, applies the spec's selection criteria with the user, and routes the resulting change through the standard PR flow. The candidate-discovery half is delegated to the fewer-permission-prompts Claude Code built-in (the spec's documented proposer); the decision to accept, narrow, or reject lives here.
Why this is a skill, not an agent¶
- Per-entry user approval is the contract. Every candidate triggers a small dialogue (accept verbatim, narrow the pattern, reject with reason); the spec's §Selection criteria explicitly forbids batched insertion.
- Externally-visible mutations need user gating.
.claude/settings.jsonlands in a PR that affects every contributor's confirmation prompts; the change is reviewed publicly, so the curator's mid-flow approvals are part of the contract. - Orchestrator pattern (per
skill-vs-agent). Candidate discovery is delegated tofewer-permission-promptsand the eventual PR is opened viapull-request-create; the skill stays in the main thread to chain those tools. - Counter-dimension considered: a narrow agent could specialise on pattern-narrowing (turning
Bash(git fetch --tags --prune)intoBash(git fetch *)), but the load-bearing dimension is interactivity, not output specialisation—skill wins.
User-language policy¶
Detect the user's language and respond in it. The committed .claude/settings.json content (pattern strings, JSON keys) and the resulting PR title/body stay English so that portfolio automation (Probot Settings App context, label-sync, PR-body validators) parses them deterministically.
Preconditions¶
Before any edit:
- Confirm the working directory is a git repository, the current branch isn't
developormain, andgit status --porcelainis clean (or the only dirty path is.claude/settings.jsonitself). - Confirm
spec/claude/permission-allowlist/<canonical_language>.mdis reachable. If missing, stop and report—without it the selection criteria are ad-hoc; this skill is the spec's implementer, not its replacement. - Confirm
.claude/settings.jsonexists (per the spec's AC #1) or scaffold it with an emptypermissions.allowarray if the user explicitly asks the skill to create the file from scratch. Never create it silently. - Verify
.gitignoredoesn't list.claude/settings.jsonitself (the file is committed); it MAY list.claude/settings.local.json(which is developer-owned).
Operations¶
1. Load the current allowlist¶
Read .claude/settings.json and parse permissions.allow. Surface the current entries to the user as a numbered list so the rest of the dialogue can reference them by index.
If permissions.allow is missing or empty, ask the user for the seed set explicitly. Never invent entries from "common defaults."
2. Gather candidates¶
Two source paths:
- User-supplied: the user names a pattern (
Bash(task lint)) or a small list. Apply the §Selection criteria below to each candidate before staging. fewer-permission-prompts-driven: invoke thefewer-permission-promptsClaude Code built-in (the spec's documented proposer in §Governance) to scan recent transcripts for read-only commands that triggered confirmation prompts. The built-in returns a prioritised candidate list; the user decides which to take.
Don't proceed past this step if the user can't articulate why they want each candidate; the spec's first selection criterion ("documented as frequently occurring in a defensible sample") is operator-judged, not skill-judged.
3. Apply the spec's selection criteria per candidate¶
For every candidate, walk the three conditions from spec/claude/permission-allowlist/ §Selection criteria explicitly with the user:
- Frequent in a defensible sample? Multiple recent sessions, multiple repositories, or a traceable spec requirement (for example
pull-request-workflow§Pre-push verification justifiesBash(task lint)). - Read-only in remote-state / secrets / shared-infrastructure terms? Local filesystem writes are acceptable as a side effect (
git fetchupdates local refs,task docswritessite/) but MUST be justified. A candidate that mutates remote state or shared infrastructure is rejected outright. - Not already in Claude Code's autoallowed set? Re-running
fewer-permission-promptsstep 4 catches redundancy. Redundant entries are omitted.
Drop the candidate at the first failed condition; record the rejection reason for the PR body.
4. Reject forbidden pattern classes outright¶
Independently of the three criteria, the spec's §Scope and location lists forbidden patterns:
- Interpreter / task-runner wildcards:
Bash(task *),Bash(npm run *),Bash(bun run *),Bash(uv run *),Bash(pip *),Bash(python -m *), and equivalents - Mutation-capable
gh/gitwildcards:Bash(git *),Bash(gh api *),Bash(gh pr *),Bash(gh repo *), and equivalents - Anything that would undermine
pull-request-workflow§Automerge trigger protocol (especiallyBash(gh pr merge *))
A user request that asks for one of these patterns gets a hard "no" with the spec citation, not a softened narrower form. The skill's job is to propose the narrowest exact form (Bash(task lint), Bash(gh pr view --json *)) when the user really needs the capability.
5. Narrow the surviving candidates¶
Per §Selection criteria's SHOULD: prefer the exact form over the prefix form. If the user has only ever observed one variant of git fetch (Bash(git fetch)), don't expand it to Bash(git fetch *) until flag variance is also observed. Walk this with the user candidate-by-candidate.
6. Apply the change to .claude/settings.json¶
Edit the file in place, preserving JSON formatting (two-space indent matches the portfolio default). Show the diff to the user. Re-run a JSON parse to verify the file is still valid.
For removals (an entry no longer used), capture the user's stated reason. The spec mandates the reason in the eventual commit message. Common reasons: "upstreamed into the Claude Code autoallow list," "command no longer used since X migrated to Y."
7. Hand off to the PR flow¶
The change ships through the standard pull-request flow per the spec's §Authoring flow integration:
- Conventional Commits type:
chorefor pure allowlist maintenance,docsif a companion spec edit is bundled (rare; usually a separate PR) - Branch prefix per
branching-model:chore/<short-slug> - The fix-PR body's Risk / rollout notes explicitly notes (a) which entries were added and the criterion that justified each, (b) which entries were removed and the reason, © confirmation that no forbidden pattern was introduced
task lintruns locally before push perpull-request-workflow§Pre-push verification
Dispatch nolte-shared:pull-request-create for the PR creation; the user confirms title and body before push.
The actual merge is pull-request-merge's job, not this skill's.
8. Quarterly drift check (recurring)¶
Per the spec's §Maintenance cadence and triggers, a drift check SHOULD run at least once per quarter (or on the next significant spec update, whichever comes first). This is a recurring process, not a standing file artefact: there is no separate per-quarter document to maintain.
Run the quarterly drift check as a normal invocation of this skill, scoped to discovery:
- Dispatch
fewer-permission-prompts(Operation 2's second source path) to scan recent transcripts for read-only commands that keep triggering confirmation prompts, and to surface entries that have since been upstreamed into the Claude Code autoallow set (candidates for pruning). - Walk the surviving candidates through Operations 3–5 (selection criteria, forbidden-pattern rejection, narrowing).
- Record the run as the quarter's drift check in the PR body's Risk / rollout notes: a one-line statement
Serves as the Q<n>-<YYYY> permission-allowlist drift check per spec §Maintenance cadence, plus the criteria walk for any added entry and the removal reason for any pruned entry. The PR (or its linked note) is the durable record that the quarter's check happened; no standalone.audits/artefact is required.
When the quarterly window is reached and no allowlist change is warranted, still record the check: a short note in the living watch-list issue (or the next chore PR touching .claude/settings.json) stating Q<n>-<YYYY> drift check ran, no change warranted satisfies the SHOULD.
When this skill is invoked from a spec-drift-audit run, treat the cadence-review change for that quarter as the quarter's drift check and say so explicitly in the PR body, so the two specs' quarterly requirements are satisfied by a single recorded run.
Q2-2026 drift check: performed under the chore/full-audit-closeout audit close-out. The three prefix-form task entries (Bash(task lint *), Bash(task test *), Bash(task docs *)) were pruned (no observed flag variance; the exact forms already cover all usage, including CI's task lint / task test); no new read-only candidates warranted addition this quarter.
Examples¶
- Read
examples/01-add-task-lint-from-fewer-prompts.mdwhen adding a new allowlist entry sourced from thefewer-permission-promptsskill output. - Read
examples/02-reject-forbidden-wildcard.mdwhen the user requests a pattern that the spec forbids (e.g. an interpreter wildcard). - Read
examples/03-prune-redundant-entries.mdwhen pruning entries that are already covered by a broader pattern already in the allowlist.
Hard rules¶
- Never edit
.claude/settings.local.jsonor~/.claude/settings.json. Both are developer-owned and out of scope per the spec's §Non-Goals. - Never insert a forbidden pattern (interpreter wildcards, task-runner wildcards, mutation-capable
gh/gitwildcards)—even when the user asks. Counter-propose the narrowest exact form. - Never copy a broad pattern from
.claude/settings.local.jsonor~/.claude/settings.jsoninto the committed.claude/settings.jsonwithout re-evaluating it against the three selection criteria; the spec's §Relationship to settings.local.json forbids the silent promotion. - Never insert a pattern that undermines
pull-request-workflow§Automerge trigger protocol or §CI gate into develop.Bash(gh pr merge *)and equivalents stay rejected even with operator override. - Never commit
.claude/settings.jsondirectly todevelopormain. Every change goes through the standard PR flow. - Never silently re-format the file beyond the change you're making—preserve unrelated entries verbatim, keep the existing indent, don't reorder unrelated keys.
- Always state the removal reason in the commit message when pruning entries—the spec mandates it.
- Always verify the file parses as valid JSON after the edit; a syntactically broken
.claude/settings.jsonmakes the harness fall back to "prompt for everything," which is worse than the original state. - When
spec/claude/permission-allowlist/disagrees with this skill, the spec wins. Propose updating this skill rather than silently diverging.
Gotchas¶
Per spec/claude/skill-management/ §Gotchas—concrete corrections to non-obvious environment facts the executing agent would otherwise get wrong.
fewer-permission-promptsis a Claude Code built-in, not a plugin skill. It lives in the harness, not underskills/. When dispatching it, use the Skill tool with the literal namefewer-permission-prompts(no plugin prefix). If the harness rejects the dispatch (older Claude Code versions don't ship it), fall back to operator-supplied candidates and report the gap..claude/settings.jsonis read at session start. Adding an entry doesn't take effect for the running session, only for future sessions. Don't expect the post-edit prompt to disappear immediately; the user has to restart Claude Code (or open a fresh session) to see the new allowlist behaviour.- The Probot Settings App doesn't manage
.claude/settings.json. The file is repository-content, not GitHub repository settings—Probot operates on.github/settings.yml, not on.claude/. Don't conflate the two surfaces; an outage of the Settings App has no bearing on the allowlist's effect. - Renovate ignores
.claude/settings.jsonby default. No automated bump exists for the patterns; drift is human-detected (thefewer-permission-promptsskill is the proposer, this skill is the curator). Don't expect a Renovate PR for "newer Claude Code autoallow list"—the autoallow list isn't versioned at the repository level. - Removing an entry never breaks past sessions. A removal-only commit is safe to merge anytime; the entry simply moves back to the per-prompt confirmation flow. The spec still requires the removal reason in the commit message so future readers see the trajectory.
Resumability¶
Per spec/claude/resumable-work/, this skill is resumable: true. State is persisted to .resume/permission-allowlist-maintain/<run-id>.yml after every successful per-candidate approval gate (each accept / narrow / reject decision in the candidate-selection walk) and after each named phase boundary (candidates gathered). On re-invocation, scan that directory for files with status: in_progress whose inputs: snapshot matches the current invocation; if one matches, prompt the operator with Resume run <run_id> from phase <phase> (last checkpoint <last_checkpoint_at>)? [resume / start-new / discard]. The state-file envelope (schema_version, run_id, inputs, phase, decisions[], status, …) and the fail-closed semantics on schema or YAML errors are load-bearing in the spec; don't duplicate those rules here.