Permissions
Make the agent structurally unable to read your secrets starter
You want .env, keys, and credentials off the table entirely — not "the model was told not to".
on the sheet: permissionsconfig
-
Deny by pattern. The rules take globs, so you can name whole shapes of file rather than enumerating them.
.cursor/cli.json
{
"permissions": {
"allow": [
"Shell(git)",
"Shell(npm)",
"Read(src/**/*.ts)"
],
"deny": [
"Read(.env*)",
"Write(**/*.key)",
"Shell(rm)"
]
}
}
A deny on Read(.env*) beats any instruction in a prompt, because it holds even when the agent has a good reason to look — a failing test that needs the database URL is exactly the moment a polite "don't read secrets" gets rationalized away.
Permissions
Give a CI agent exactly one job intermediate
An agent runs on every PR and should comment — but never push, never branch, never merge.
on the sheet: permissionsheadless
-
Deny git and gh outright rather than trying to carve out gh pr comment while blocking gh pr merge — a Shell() rule keys on the command's first token (the optional command:args form narrows it, but a denylist of subcommands is one you'll always be an entry behind on). Keep version control out of the agent's hands entirely.
.cursor/cli.json
{
"permissions": {
"allow": ["Read(**)"],
"deny": ["Shell(git)", "Shell(gh)", "Write(**)"]
}
}
-
Let CI fetch the diff and post the comment; the agent only judges it. This is Cursor's own "restricted autonomy" pattern for production CI — critical operations stay deterministic and auditable, the agent handles the analysis it's actually good at.
diff=$(git diff origin/main...HEAD)
findings=$(agent -p "Review this diff like an owner. Cite every finding as file:line.
Diff:
$diff" --output-format json | jq -r '.result')
gh pr comment "$PR_NUMBER" --body "$findings"
The two steps fail differently on purpose. Denying git/gh in the agent's own config is what holds even if the prompt is silent or the model decides pushing a fix would be helpful; running the actual git diff and gh pr comment as plain CI steps means the boundary isn't the permission system's job to enforce in the first place.
GitHub Actions
Approve one MCP tool, not the whole server intermediate
An MCP server bundles tools you don't want to hand the agent blanket access to — --approve-mcps waves through every server, Mcp(*:*) waves through every tool on every server.
on the sheet: mcppermissions
-
List what the server actually exposes before writing the rule — tool names, and their argument shapes, aren't always what you'd guess from the server name.
agent mcp list-tools datadog
-
Allow that one tool by its server:tool name. Every other tool on that server, and every other server, still needs its own rule.
.cursor/cli.json
{
"permissions": {
"allow": ["Mcp(datadog:search)"]
}
}
--approve-mcps and Mcp(*:*) are both blanket switches — fine for a one-off local session, wrong for anything unattended. Naming the exact server:tool pair means the server adding a second tool later doesn't silently inherit trust it never earned.
Permissions · MCP
Try a command in isolation before you trust it in every session intermediate
You're about to add a broad Shell() allow rule — Shell(npm), Shell(curl:*) — and want to see what the command actually touches before granting it blanket access for good.
on the sheet: permissions
-
agent sandbox run executes one command in an OS-level sandbox scoped to the workspace, with network off by default — no session, no model call, no permission rule needed just to try it.
agent sandbox run npm install
-
Opt in to network for just this trial if the command needs it, and block paths you don't want touched even inside the workspace.
agent sandbox run --network --blocked-patterns "**/.env*,**/*.key" npm install
-
Only once you've watched it behave do you widen the real permission rule — scoped to the command and the args you actually tested, not the bare command name.
.cursor/cli.json
{
"permissions": {
"allow": ["Shell(npm:install)"]
}
}
agent sandbox run and permissions.allow solve different problems: the sandbox bounds what a command can do for one trial run, the allow rule decides what runs without asking forever after. Testing in the sandbox first means the permission you add is informed by what you actually saw the command do, not by how trustworthy its name sounds.
Permissions · CLI parameters
Headless & CI
Pipe the agent without remembering a flag starter
You want the agent's answer in a shell variable or another process.
on the sheet: headlessflags
-
Print mode is inferred when stdout isn't a TTY — piping is enough, -p is belt and braces.
agent -p "List every TODO in src/ with its file and line." > todos.txt
-
For machine-readable output, ask for JSON explicitly. --output-format is only valid in print mode.
agent -p "Is the build config valid? Answer yes or no." \
--output-format json | jq -r '.result'
-
stream-json emits NDJSON — one line per assistant message — so you can act on progress instead of waiting.
agent -p "Refactor the auth module." --output-format stream-json \
| while read -r line; do echo "$line" | jq -r '.type'; done
The inference is a nice touch but it cuts both ways: a script that works interactively can silently change behavior when its stdout gets redirected. If you depend on the format, name it.
Output format
Fan out headless agents without their edits colliding intermediate
You're kicking off several unattended agent -p runs against the same repo at once — one per ticket, say — and don't want them writing to the same working tree.
on the sheet: launchheadless
-
Give each run its own worktree. Edits land under ~/.cursor/worktrees/<repo>/<name> instead of racing on the same files in your checkout.
agent -p --force --worktree "fix-auth-flake" "Fix the flaky auth test in tests/auth.spec.ts" &
agent -p --force --worktree "upgrade-runner" "Upgrade the test runner to the latest major version" &
wait
-
Pin the base explicitly with --worktree-base when you don't want every run branching off your current HEAD — useful mid-rebase, or kicking off runs from a stale local branch.
agent -p --force --worktree "hotfix" --worktree-base origin/main "Backport the null-check fix from PR #482"
-
If the repo defines setup commands in .cursor/worktrees.json (installing deps per worktree, say), add --trust — those scripts only run once the workspace is trusted, and headless has nobody at the keyboard to click through that prompt.
agent -p --force --trust --worktree "fix-auth-flake" "Fix the flaky auth test"
Two headless agents writing to the same checkout is a plain file race. --worktree sidesteps it structurally instead of trusting the agents to coordinate — same -p --force semantics you already use for headless, just a different cwd per run.
Using Agent in CLI · CLI parameters