Streamline approvals
Skill designgrappler/agent-os/claude/skills/streamline-approvals
Agent OS is a management layer for complex, multi-agent AI projects. Instead of letting agents get lost in endless conversational loops, it enforces a disciplined workflow: upfront planning, shared context, scoped work tracks, and strict quality gates — so every agent stays focused, coordinated, and aligned from start to finish.
npx -y skills add designgrappler/agent-os --skill streamline-approvalsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Scans recent Claude Code transcripts, identifies common read-only tool calls, and writes an optimized allowlist to `~/.claude/settings.json` (global) to reduce permission prompts across all projects.
SKILL.md
16.1 KB, as published. Nobody here has run it
Streamline Approvals
Scans recent Claude Code transcripts, identifies common read-only tool calls, and writes an optimized allowlist to ~/.claude/settings.json (global) to reduce permission prompts across all projects. Focuses exclusively on read-only operations — nothing that writes, deletes, pushes, or installs.
Profile Argument-Detection (runs before everything else)
If the skill is invoked with an argument, process it here and exit — do NOT continue to the transcript scanner.
auto argument
- Read
~/.claude/settings.json. If the file does not exist, create it with{"permissions":{"allow":[]}}. Ifpermissions.allowis missing, create it as an empty array. - Use
jqto append"Agent"and"Task"topermissions.allowif not already present. De-duplicate. Write back atomically (write to a temp file in the same directory, thenmv— never use>redirect). - Verify:
jq '.permissions.allow | index("Agent")' ~/.claude/settings.jsonmust return a non-null index. Same for"Task". Verify all other top-level keys are unchanged. - Report:
Applied 'auto' profile. Agent and Task allowlisted in ~/.claude/settings.json. Sprint Coordinator Agent spawns will proceed without per-call prompts. - Exit. Do not run the transcript scanner.
gated argument
- Read
~/.claude/settings.json. If the file does not exist orpermissions.allowis missing or empty, reportAlready in 'gated' posture — no Agent allowlist entry present.and exit zero (idempotent). - Use
jqto remove any element ofpermissions.allowequal to"Agent"or"Task". Preserve all other entries. De-duplicate. Write back atomically. - Verify:
jq '.permissions.allow | index("Agent")' ~/.claude/settings.jsonmust returnnull. Same for"Task". Verify other entries are preserved. - Report:
Applied 'gated' profile. Agent allowlist entry removed from ~/.claude/settings.json. Sprint Coordinator Agent spawns will now require a per-call permission prompt. - Exit. Do not run the transcript scanner.
Unknown argument
If the argument is any other non-empty string, surface this error and exit — do NOT run the transcript scanner:
Unknown profile: <arg>. Valid profiles: auto, gated. Run /streamline-approvals (no argument) for the transcript scanner.
No argument
Fall through to the transcript scanner (Steps 1–10 below).
Auto-Trigger
Invoke when the user says:
- "reduce permission prompts", "fewer prompts", "stop asking me about permissions"
- "add to allowlist", "update settings allowlist"
- "scan transcripts for permissions"
Rules
- Read-only only. Never allowlist a command that writes, deletes, renames, pushes, merges, installs, or runs a build/test with side effects. When in doubt, leave it out.
- No arbitrary code execution. Never allowlist a wildcard pattern for interpreters (
python3,node,bun,deno,ruby, etc.), shells (bash,sh,zsh,eval,exec,ssh), package runners (npx,bunx,uvx), or task-runner wildcards (bun run *,npm run *,make *). An exact form likeBash(bun run typecheck)is fine;Bash(bun run *)is not. - Merge, never overwrite. Preserve all existing keys and existing
permissions.allowentries. De-duplicate. Never reorder unrelated fields. - Global settings. Write to
~/.claude/settings.json— not.claude/settings.json, not.claude/settings.local.json. Patterns found across multiple projects belong globally; writing per-project leaves prompts unresolved everywhere else. - Scripts directory scope. When Step 9a extracts a compound command into a script, write the script to
scripts/<name>.shinside the current project's working directory (the per-project repo). Do not write extracted scripts to global paths (~/.claude/,/usr/local/bin/, etc.). Thescripts/directory and its contents belong to the project repo, not the global Agent OS install.
Protocol
Step 1 — Locate transcripts
Session transcripts live at ~/.claude/projects/<sanitized-cwd>/*.jsonl. Each line is a JSON object. Tool calls appear as assistant messages with message.content[] entries of type: "tool_use". The name field identifies the tool (e.g. "Bash", "mcp__slack__slack_read_thread"); for Bash, input.command is the shell string.
Scan recent transcripts across the user's full projects dir — not just the current project — so the allowlist reflects actual usage. Cap at the 50 most-recently-modified JSONL files.
Step 2 — Extract tool-call frequencies
-
Bash calls: parse
input.commandwith the following normalizations before recording:-
Env-var prefixes — if the command starts with
KEY=valueassignments (e.g.GH_HOST=github.com gh run list), strip the prefix to identify the base command, but record the original prefix separately. The base command is used for read-only classification; the prefix is preserved in the generated pattern (e.g.Bash(GH_HOST=github.com gh *)). -
git -C <path> <subcommand>— if the command matchesgit -C <path> <subcommand> ..., extract<subcommand>as the operative token. Record asgit -C * <subcommand>for classification. These are NOT auto-allowed by Claude Code even when the subcommand is read-only — they need explicit allowlist entries. -
Compound commands (
&&,||,|,;,for/whileloops, subshells) — flag these separately as "compound — cannot allowlist as-is." Do not attempt to parse constituent commands. Normalize whitespace (collapse all runs of whitespace to a single space, trim leading/trailing whitespace) to produce a canonical form for each compound, then count occurrences of each canonical form across all scanned transcripts. Collect the resulting canonical-form → count map in a separate list for Step 9a and the Step 10 gap report. -
Everything else — take the leading command token (handling
sudo,timeout). Record the command + first subcommand pair (e.g.git status,gh pr view,ls).
-
-
MCP calls: record the full tool name (e.g.
mcp__slack__slack_read_thread). -
Built-in tool calls: record other tool names that aren't Bash or MCP — specifically
WebFetch.
Count occurrences across all scanned transcripts.
Step 3 — Filter to read-only (plus workspace management)
Keep commands that don't mutate state, plus workspace management operations that are safe to allowlist even though they touch the filesystem. Examples of safe read-only commands: git status/log/diff/show/branch, gh pr view/list/diff, gh issue view/list, gh run view/list, rg, grep, find, bun run typecheck, bun run lint, docker ps/logs, kubectl get/describe, ps, env, printenv, WebFetch, any MCP tool with read/get/list/search/view/retrieval in its name.
Workspace management operations to include: git worktree add/remove/list/prune/move/lock/unlock — these manage local working trees and are safe to allowlist as Bash(git worktree *).
Step 4 — Drop auto-allowed commands
These never prompt in Claude Code — skip them:
- Always auto-allowed (any args):
cat,head,tail,wc,stat,ls,cd,find,diff,echo,printf,date,which,file,grep,egrep,fgrep,rg,jq,sort,uniq,tree,ps,du,df, and most other standard read-only Unix utilities. - Auto-allowed with zero args only:
pwd,whoami,alias. - All git read-only subcommands:
git status,git log,git diff,git show,git blame,git branch,git tag,git remote,git ls-files,git stash list,git reflog,git worktree list, etc. git worktree add/remove/prune/move/lock/unlock— NOT auto-allowed. Even though worktree management is safe to allowlist, onlygit worktree listis auto-allowed. All othergit worktreesubcommands prompt and should be suggested asBash(git worktree *).git -C <path> <subcommand>— NOT auto-allowed. Even when the subcommand is read-only, Claude Code does not auto-allowgit -Cvariants (the-Cflag shifts the subcommand to the third token position). Do not drop these — let them flow through as allowlist candidates with patternBash(git -C * <subcommand> *).- All gh read-only subcommands:
gh pr view/list/diff/checks/status,gh issue view/list,gh run view/list,gh repo view,gh release view/list,gh auth status, etc. - Docker read-only:
docker ps,docker images,docker logs,docker inspect.
Source of truth: If you're unsure whether a command is already covered, grep src/tools/BashTool/readOnlyValidation.ts (READONLY_COMMANDS, READONLY_NOARGS, READONLY_EXACT, COMMAND_ALLOWLIST) and src/utils/shell/readOnlyCommandValidation.ts (GIT_READ_ONLY_COMMANDS, GH_READ_ONLY_COMMANDS, DOCKER_READ_ONLY_COMMANDS) in the Claude Code repo. Never guess — check the source.
Step 5 — Pick the pattern form
Use the narrowest pattern that covers observed usage:
- Many variants of the same Bash command (
git log,git log --oneline,git log main..HEAD) →Bash(git log *)(space before*is required for prefix matching) - Single exact Bash invocation →
Bash(foo)with no wildcard - Env-var prefixed commands — preserve the prefix in the pattern.
GH_HOST=github.com gh run list→Bash(GH_HOST=github.com gh *). Use a wildcard after the base command if multiple variants appear; use an exact form if only one invocation was observed. git -Cvariants — useBash(git -C * <subcommand> *). One entry per distinct read-only subcommand observed (e.g.Bash(git -C * status *),Bash(git -C * log *)).- MCP tools — prefer server-level entries. If multiple tools from the same server appear (e.g.
mcp__figma__get_fileandmcp__figma__get_component), use the server prefixmcp__figma— it covers all tools from that server and avoids a growing list of individual entries. Use a specific full tool name only if you want to allow one tool from a server while leaving others unapproved. WebFetch— use the bare tool name verbatim.
Step 6 — Cross-reference current allowlist
Read ~/.claude/settings.json and extract the current permissions.allow entries. For each candidate pattern from Steps 3–5, mark it as one of:
- Already covered — an existing allowlist entry already matches it (exact or prefix). Skip entirely — don't re-add or surface.
- Approved manually — the pattern appears in transcripts, is not auto-allowed, and is not in the current allowlist. This means the user has been approving it via prompt every time. These are the highest-priority entries to add.
- New suggestion — a read-only pattern that appears frequently but hasn't been explicitly encountered as a prompt yet.
Step 7 — Prioritize
- Approved manually entries first (sorted by count descending) — these directly eliminate recurring prompts.
- New suggestions second (sorted by count descending). Drop anything with fewer than ~3 occurrences. Cap the combined list at ~20 entries.
Step 8 — Present to user
Show the prioritized list as a markdown table before writing anything, with a tier label:
| # | Pattern | Count | Tier | Notes |
|---|---|---|---|---|
| 1 | Bash(npx tsc --noEmit) | 34 | Approved manually | TS type-check |
| 2 | mcp__figma | 18 | Approved manually | Figma reads |
| 3 | Bash(curl -s *) | 12 | New suggestion | API reads |
Step 9 — Merge into ~/.claude/settings.json
Create the file if it doesn't exist. Preserve existing keys and permissions.allow entries; de-duplicate; don't remove anything; don't touch permissions.deny or permissions.ask or any other field.
Step 9a — Compound Script Extraction
Using the canonical-form → count map built in Step 2, split the compound commands into two tiers:
Tier A — extraction candidates (3 or more occurrences):
For each canonical form that appears 3+ times across the scanned transcripts:
- Propose a descriptive script name under
scripts/that captures the command's purpose (e.g.scripts/check-status.sh,scripts/verify-build.sh). - Show the proposed script contents — a minimal shell script wrapping the compound exactly as observed (canonical form). Example:
Proposed: scripts/check-status.sh #!/usr/bin/env bash set -euo pipefail git remote -v && GH_HOST=github.com gh auth status 2>&1 | head -10 - Ask the user to approve, decline, or rename each proposed script individually. Wait for the user's response before proceeding to any writes.
- For each approved script:
- If the
scripts/directory does not exist in the current project repo, create it silently (mkdir -p scripts). - Write the script file to
scripts/<name>.sh. - Make it executable (
chmod +x scripts/<name>.sh). - Add
Bash(bash scripts/<name>.sh)to the allowlist candidates for Step 9 (merge into~/.claude/settings.json).
- If the
- Declined scripts are skipped entirely — move them to the "CANNOT ALLOWLIST" bucket in the Step 10 gap report.
- Renamed scripts use the user-supplied name in place of the proposed name; otherwise the process is identical.
Tier B — low-frequency compounds (fewer than 3 occurrences):
Do not propose script extraction for these. Continue to report them as un-allowlistable in the Step 10 gap report under the "CANNOT ALLOWLIST" bucket (existing behavior preserved exactly).
Step 10 — Report back
Tell the user: what was added (count + examples), what was already in the allowlist, and what was skipped and why (e.g. "dropped rm and git push — not read-only; dropped cat/ls/git status — already auto-allowed").
Known gaps — commands that cannot be allowlisted:
If compound commands were detected in Step 2, report them here:
EXTRACTED TO SCRIPTS (compound commands, 3+ occurrences — extracted in Step 9a):
- `git remote -v && GH_HOST=github.com gh auth status 2>&1 | head -10`
→ scripts/check-status.sh (Bash(bash scripts/check-status.sh) added to allowlist)
CANNOT ALLOWLIST (compound commands, fewer than 3 occurrences or declined in Step 9a):
These commands triggered prompts but cannot be safely pattern-matched:
- `ls ~/.claude/skills/ && cat CLAUDE.md | grep skill`
Fix: extract into a named script and allowlist the script instead.
Example:
scripts/check-skills.sh → Bash(bash scripts/check-skills.sh)
Compound constructs (&&, ||, |, ;, for/while loops) always prompt
regardless of whether the constituent commands are safe.
Named Profiles
Two predefined presets map the old operatingMode labels to approval-frequency configurations. Run /streamline-approvals and invoke a profile name to batch-apply the preset.
auto profile
Adds Agent-tool spawn to the approval allowlist — equivalent to the auto-approve mode posture. With this profile active, the Sprint Coordinator's Agent tool calls proceed without a per-spawn permission prompt.
To apply: Run /streamline-approvals auto
Effect: adds Agent (or the equivalent Agent-tool pattern) to ~/.claude/settings.json permissions.allow. The Sprint Coordinator can spawn Specialists inline without per-call prompts.
gated profile
The default posture — no pre-approved Agent spawns. The Claude Code permission prompt fires on each Agent tool call. Equivalent to the post-T23.A.1 gated-approve mode behavior.
To apply: Run /streamline-approvals gated
Effect: removes any Agent-tool spawn entry from ~/.claude/settings.json permissions.allow (if present). Every Sprint Coordinator Agent spawn requires an inline approve/deny decision from the Conductor.
When to use which
| Profile | Use when |
|---|---|
auto | You've established trust in the pipeline; want hands-off sprint execution |
gated | You want per-spawn approval; high-stakes sprints; onboarding a new Specialist |
The profile shorthand (gated/auto) maps to the mode labels (gated-approve/auto-approve) — gated selects gated-approve mode, auto selects auto-approve mode.