When not to automate it
One prompt, six harnesses, nobody watching: five exited 0 and three of those did nothing at all. How to tell which jobs survive being run unattended.
The run was green and the file wasn't there
We created an empty directory and ran Claude Code in it with one job: create hello.txt containing hi. No permissions flag, no bypass flag, exactly as you'd run any tool from cron or in CI.
claude -p "Create a file named hello.txt in the current directory containing exactly: hi" \
--model haiku --output-format jsonIt returned exit code 0, is_error=false, subtype success, and cost $0.0248, but there was no hello.txt on disk. It turned out the model asked a human if it can create this file at the very end — but there was nobody to ask, so the tool refused, told the shell nothing about it, and finished its work.
I need your permission to write the file. Please approve the request to create `hello.txt`.We weren't actually angry with Claude Code for that; it's usually the opposite — the agent is doing nothing while everything downstream is told it worked.
One prompt, six harnesses, nobody at the keyboard
Okay, let's explore. In six different environments, we ran the very same prompt in an empty directory, with the stdin closed (so it can't ask us), and without using the bypass flag (so it asks if it can). Here's what happened:
| Harness | Exit | File written | What came back |
|---|---|---|---|
| Claude Code 2.1.221 | 0 | no | asked for permission at the end; a permission_denials entry in the JSON output |
Codex 0.146.0 (exec) | 0 | no | banner reads approval: never, sandbox: read-only, then "I can’t create files in this workspace because it’s currently mounted read-only." |
| Copilot CLI 1.0.77 | 0 | no | "Permission denied and could not request permission from user" — twice |
| cursor-agent 2026.07.23 | 1 | no | "Workspace Trust Required", didn't get to the model |
| Antigravity 1.1.10 | 0 | yes | but in ~/.gemini/antigravity-cli/scratch/ instead of the target directory |
| Kimi Code 0.31.1 | 0 | yes | in the working directory |
So: three outcomes, five zeroes. And if you were to run this in your shell and chain it with &&, or put it in a set -e script, or make it part of a CI pipeline based on success, you wouldn't know the difference between the Codex row and the Kimi one. The only one that visibly failed (cursor-agent) actually failed for a different reason — it doesn't trust directories it hasn't met before, a first-run gate rather than an opinion about the task.
For example, Antigravity — green run, real file, wrong place. We ran it again with --add-dir pointing to the actual directory, and this time it said it can't, because the tool needs the write_file permission and headless mode can't ask for it (so the version that seemed to work was the one creating a file outside the project).
What headless actually asks you to decide
What unattended mode asks from you is to answer all the questions before they are asked. In interactive mode, it's one question per decision; in unattended mode, you need to have an opinion about everything, before even knowing what everything is. And every single tool provides you with a flag that lets you stop asking questions, and their own help text says it like it is:
| Harness | The flag | What its help says |
|---|---|---|
| Claude Code | --dangerously-skip-permissions | "Bypass all permission checks. Recommended only for sandboxes with no internet access." |
| Codex | --dangerously-bypass-approvals-and-sandbox | "Skip all confirmation prompts and execute commands without sandboxing. EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed" |
| Copilot | --yolo / --allow-all | "Enable all permissions (equivalent to --allow-all-tools --allow-all-paths --allow-all-urls)" |
| cursor-agent | -f, --force (--yolo is an alias) | "Force allow commands unless explicitly denied" |
| Antigravity | --dangerously-skip-permissions | "Auto-approve all tool permission requests without prompting" |
| Kimi Code | --auto | "Start in auto permission mode: fully autonomous, the agent will not ask questions." |
That's three "dangerous"es and three --yolo-es (Kimi's is -y, which is a step weaker than the listed --auto). If you were to write this down in your code, you'd basically say: "In this run, it can do whatever it needs to in order to fulfil the task". Like this r/ClaudeAI post from June 2026, when somebody asked to test sth before creating a PR, and Claude Code read their .ssh config, connected to the production VPS, stopped the app for more than 300 users, and started it on new code. The task was too vague, and the agent wasn't evil.
What's most important in the top comment is that an instructions file is a seatbelt, not a boundary. If the model misreads the task, the environment should still make it hard or impossible to do what's wrong.
A denied tool is not a denied outcome
For example, here's Copilot trying twice — first with its built-in file creation tool, then with a shell command — and both times saying "Permission denied".
✗ Create hello.txt
└ Permission denied and could not request permission from user
Writing the file with a shell command (printf) to avoid the create tool permission error and
ensure the content is exactly "hi" (no trailing newline). Running the command and listing the file.
✗ Create hello.txt with exact content 'hi' and list it (shell)
│ printf 'hi' > /private/tmp/wnta-copilot/hello.txt && echo created && ls -l
│ /private/tmp/wnta-copilot/hello.txt
└ Permission denied and could not request permission from userIt can't create a file because you didn't allow it to, but also because you haven't allowed the shell. What the model is not doing is bypassing your guardrail; it's trying to complete the task through every avenue that remains open. So if you were to define an allow-list, you need to do it for an effect you're ready to happen, rather than for a tool that's usually used to achieve it.
What's more, it's actually the other way round. GitHub's May 2026 post on reviewing agent PRs points out that an agent that can't get past your CI has an obvious route to green: remove the test, skip the lint step, append || true.
The two questions that decide it
So now you know what to ask yourself: is a wrong run reversible (can be reverted), and will you notice. Let's have a look at the matrix:
| you'd notice | you wouldn't | |
|---|---|---|
| reversible | automate | automate, but check the effect instead of the exit code |
| irreversible | gate it, the agent prepares and a human initiates | don't |
There's only one real "don't" here, and it's way more significant than it seems at first glance. It's because of unattended mode — green logs are unread by default. The first run cost 2c, said it succeeded, didn't create anything, and the only way to know is if you go through the JSON and find the permission_denials key.
Detectability is sth you can come up with, but reversibility mostly isn't — it depends on what the job touches, and is influenced by changing where it points.
Four jobs that fail one of those
But there are some types of jobs that don't pass at least one of these tests:
Exit code 0, actual cost, nothing done; if you were to automate this and rely on the exit code, you'd be automating a coin flip that doesn't have a number on it
Answer lives outside the repository — why is this happening, what does the client actually mean, which of two sensible options did we already choose once? The agent will confidently pick one side of the context you've committed and tell you the answer
Happens twice a year — involves setup costs, then maintenance costs (two of the six shipped a new version during the few days we spent writing this), and then monitoring its output forever; a rare job pays for none of the three
Wrong output that reaches somebody outside the team — a client's reply, a comment under a stranger's PR, an auto-closed issue. Reverting a commit is free, but being the person who owns the repo whose bot closes people's issues is not
And one that isn't about the agent at all: if you're reaching for automation because a process is broken — flaky tests somebody keeps re-running, a deploy that needs babysitting — an agent makes the brokenness quieter rather than smaller.
Move it down a rung instead of answering yes or no
But as we said, it's not a binary choice. There's a ladder:
read-only (observes and reports, doesn't modify anything)
proposes (writes a comment, draft PR, label, or diff, and a person approves)
acts within an allow-list (does the thing, and only the thing)
acts (the aforementioned flags)
And it's one flag away to rung 1 in all of them:
| Harness | Read-only / plan |
|---|---|
| Claude Code | --permission-mode plan |
| Codex | -s read-only, which is actually the default in codex exec, as you can see in the banner |
| Copilot | --plan or --mode plan |
| cursor-agent | --plan — "read-only/planning (analyze, propose plans, no edits)" |
| Antigravity | --mode plan |
| Kimi Code | --plan |
Rung 3 is actually where unattended agents earn, and it's the cheapest option if there's a permission layer in your harness. Here's Claude Code again:
2c for the run above (not sure if you remember but it said success and didn't create anything), $0.0074 for the very same prompt with
--allowedTools "Write", which created the file and had an emptypermission_denialsarray
And Copilot:
--allow-tool='write'was enough, 0.28 AI credits, even though its help text calls--allow-all-tools"required for non-interactive mode"
Kimi doesn't even have an allow-list for you to configure, which is actually a type of answer in itself.
But rung 2 is the one that's usually skipped, and it fits most of the cases we've mentioned above — a bot that creates a draft PR instead of creating a commit, or adds a comment instead of closing an issue, is reviewable by design rather than by discipline, and the review it enforces is the one you anyway owed earlier, for a thing that's still rejectable.
Claude Code: the denial is in the JSON, not the exit code
The thing is that when it comes to Claude Code, if you run it and it doesn't have access it shows it in the output, but it's still 0 exit code and successful type, so it's still charged. The run itself is indistinguishable from the successful one to anything that comes after it. The only difference is this permission_denials array
So if you need to set up a CI gate for it, you could have it run the command and then check that this array is empty
claude -p "…" --output-format json | jq -e '.permission_denials | length == 0'Each of these denial records contains the name of the tool, the id of its use, and the very input it tried to use. So in this case, a Write tool with the path already set to file and the content also pre-composed. This way you can log the exact command it tried to run, and then turn it into an allow rule instead of trying to guess
The rungs.
plan mode — read-only
allowedTools— a strict allow-list (like--allowedTools "Bash(git diff *),Edit"), taking permission-rule syntax; the headless docs are emphatic about the space before the wildcard, because without itBash(git diff*)"would also matchgit diff-index"dontAsk— locked-down CI, won't let you do anything it doesn't allow in itspermissions.allowrules and the read-only commands
--bare
The docs say this is useful in scripted scenarios and that it "will become the default for -p in a future release". In CI we'd include it too, as it stops your run from quietly picking up a colleague's hook: it skips hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads and CLAUDE.md auto-discovery (skills are still accessible via slash-name)
The gotcha, and it's a mean one
The most important part of the help text is that it ignores settings files if they're not valid, without saying anything: "Settings files that fail validation are silently ignored in this mode (no error dialog is shown)." Your permission rules live in a settings file, so a settings file that doesn't validate doesn't fail the run — the rules in it simply aren't there. Validate it as a separate step in CI before you rely on it
What we ran. Claude Code 2.1.221, under /tmp with the haiku model, once without any permission flags and once with --allowedTools "Write". The user settings.json was empty (so no permissions block or defaultMode), so it's the stock behaviour. We never passed the bypass flag, we only quoted its help text
Codex: the run prints its own policy, and the default is read-only
Announces its policy in the beginning and starts as the most restrictive of the bunch:
workdir: /private/tmp/wnta-codex
model: gpt-5.6-terra
provider: openai
approval: never
sandbox: read-onlySo the run started with a banner showing workdir, model, provider, the approval set to never, and the sandbox set to read-only. That means it can't request any permissions, and can't write, so it said the workspace is read-only and stopped, which is why no file appeared
Both of these settings were defaults, and the local config.toml contained four trust_level entries for other projects (not relevant), and didn't have sandbox or approval keys. So this is the least-privileged start point.
What's valuable here is that it shows the policy in the beginning — if you had that in your CI logs you could always know what it was and wouldn't need to analyse it later
The rungs.
read-only sandbox
workspace-write sandbox
full-access mode, which is not what you want
--add-dirfor real needs of writing things outside of the workspace
It's worth noting that codex exec --help on 0.146.0 doesn't mention -a/--ask-for-approval at all — it's a top-level command's one, with three options: untrusted / on-request / never. The subcommand has its own ways to alter the policy, like --config or --profile. It also has --ignore-rules, which ignores user and project execpolicy .rules files, and --ignore-user-config. Instead of these two guardrail-removing flags we'd suggest using --strict-config, which fails on keys this version doesn't know about — it's the right thing to do for a run nobody watches
There's no permission_denials array in the plain-text output, only as prose, but it does have the --json mode, which emits JSONL events (inspectable), and -o, which writes the last message to a file (also inspectable). But what's left is asserting the effect afterward, which is the most robust way
What we ran. Codex 0.146.0, codex exec "<prompt>" in an empty git repository under /tmp, no flags. We never passed the bypass flag, we just quoted its help text
Copilot: it says it out loud, and a narrow allow-list is enough
States the denial plainly, and a scoped allow-list does the job:
✗ Create hello.txt
└ Permission denied and could not request permission from userThat's the most clear refusal of the bunch, it prints a line for each attempt saying permission is denied, and that it can't ask the user about it. Then it tried the same task via shell, and got the same thing:
✗ Create hello.txt with exact content 'hi' and list it (shell)
│ printf 'hi' > /private/tmp/wnta-copilot/hello.txt && echo created && ls -l
│ /private/tmp/wnta-copilot/hello.txt
└ Permission denied and could not request permission from userSo it tried again, denied again, and printed the commands a person could run instead. 0.72 AI credits wasted, but still exit 0.
The rungs.
plan mode — read-only
allow-tool — scoped allow-list
The help text for this version calls --allow-all-tools "required for non-interactive mode", but on 1.0.77 a bare --allow-tool='write' completed the same task for us, at 0.28 AI credits. So the help is slightly exaggerated, it's best to aim narrow.
For instance, you can allow a scoped git shell pattern but explicitly deny git push:
copilot -p "…" --allow-tool='shell(git:*)' --deny-tool='shell(git push)'The help text for permissions says that deny rules beat allow rules, including allow-all-tools. So even if you were to use the latter, you'd still have the break
There are two levers which are easy to confuse:
allow-tool and deny-tool are about the approval prompt
available-tools and excluded-tools are about what the model can see
If a tool is not supposed to be used, removing it from the model's list is better than denying it after selection
--max-ai-credits
The minimum is 30, and the help text for this version calls it a soft cap: "usage is known only after a model response returns. A response can therefore exceed or exhaust the limit before the CLI can observe that it has done so; the next model call is then blocked." So it stops the next call, not the expensive one you're already in
Command sandbox
This is an experimental feature in the tool, and by default it's turned off. The help text says that when it's off the commands run directly on your machine with your account having its full access
What we ran. GitHub Copilot CLI 1.0.77, copilot -p under empty /tmp directories twice, once with no permission flags and once with --allow-tool='write'. The next day 1.0.78 came out with byte-identical help text, so these flags apply to both. We never passed --yolo or --allow-all, we only quoted their help text
Cursor: the only one that failed loudly, for a reason you should know about
The only harness that failed visibly, for a reason worth understanding:
⚠ Workspace Trust Required
Cursor Agent can execute code and access files in this directory.
Do you trust the contents of this directory?So it exited non-zero, before even reaching the model. The trust is granted per directory, so a fresh CI checkout is a directory it hasn't met, and that's why it exited 1 and asked instead of doing anything
But at the same time this is the only failure in the matrix a pipeline would notice. It's also good to know the prompt says you can --trust the folder, or --yolo/--force the command. These are not the same things:
--trustis about the folder-f/--forceand--yoloare about forcing the command
So a pipeline should use --trust, but it's good to know that it can be pushed into using the other two
The help text for --print also says that print mode "Has access to all tools, including write and shell." That means -p is not a restricted mode here the way it is in the others — once you trust a directory, an unattended run is holding write and shell by default rather than by opt-in
The rungs.
plan mode — read-only planning (analysing and proposing, no editing)
ask mode — read-only asking
sandbox — enabled to override whatever config says
--worktree— to prevent the run from touching the branch you care about
--auto-review
Looks like a middle option, where the server-side classifier auto-approves harmless calls and asks about the rest. But the asking part is useless in an unattended scenario
What we ran. cursor-agent 2026.07.23-e383d2b, with a prompt under an empty git repository under /tmp, no flags, stdin closed. This is where we stopped exploring it, as checking what happens after trusting it would mean running an unattended agent with write and shell access, which isn't a thing we'll do to demonstrate a point. So the tool-access claim above is the help text, not something we observed. The machine's cli-config.json had approvalMode set to allowlist, but that never came into play, as the trust gate fires first
Antigravity: check where it thinks it is before you check anything else
Without a workspace it did write the file, and told us where:
Since there is currently no active workspace set, the file was created in your default scratch
directory `/Users/…/.gemini/antigravity-cli/scratch`. You may want to set your active workspace
to this directory or your desired project directory.So the run without --add-dir created the file inside the CLI's scratch directory under ~/.gemini and said that with no active workspace it defaults there, suggesting setting one. Exited 0, wrote a real file, into the wrong place, in a pipeline this would be a passing step that doesn't touch the repo, with the explanatory part lost in the log
The rungs.
plan mode — read-only
accept-edits — when editing is the goal
--sandbox— for terminal restrictions--add-dir— must-have, given the no-workspace result
jetski: no output produced — a tool required the "write_file" permission that headless mode
cannot prompt for, so it was auto-denied. Add an allow-rule under permissions.allow in
settings.json (e.g. write_file(<target>)). Alternatively, re-run with
--dangerously-skip-permissions to auto-approve all tools.And here's what happens if you point it at a real directory — it auto-denies the same task and says why, mentioning both the reason (the write_file permission headless mode can't prompt the user) and the remedy (a permissions.allow scoped allow-rule for that path in settings.json, or alternatively using --dangerously-skip-permissions to automatically approve everything)
Given these are your options, we'd go with the scoped allow-rule for the path — the narrow step; the other option is the top of the ladder
--print-timeout
The default is 5 minutes — enough to investigate, too little if you need to build anything. It's best to increase it on purpose rather than discovering it during the night
What we ran. Antigravity CLI 1.1.10, agy -p in an empty git repository under /tmp, once with no flags and once with --add-dir. The user settings.json held only an auth block and the temp directory wasn't in trustedFolders.json, so this was the default behaviour. The stray scratch file was deleted afterwards, and we never passed --dangerously-skip-permissions
Kimi Code: print mode is already the permissive mode
One of the two that simply ran it. kimi -p with no -y, no --auto and no permissions config anywhere wrote the file in the working directory and exited 0
Then we asked it to run a shell echo, and it ran the command and returned its output. Both without any flags or prompts
That means that in this case print mode auto-approves both file writes and shell commands, which are the two things we tested
What you put back
This way it's opposite to other tools — with them you usually wonder if there's a flag you could set to bypass the approval prompt, here it's about what you might want to put back. For instance:
--plan— read-only planning (analysing and proposing, no editing)
But in this case the local config.toml held providers, models, and a thinking table with nothing on permissions, so there was nothing to either relax or tighten
--yolo / --auto
These are two similar-sounding flags:
-y/--yoloauto-approves ordinary tool calls, but the agent can still ask questions--autostarts in fully autonomous permission mode and asks no questions
So a pipeline would want the latter (it needs the run to be unattended), but it's good to know it's different from the first one. Neither of our runs was asked a question, so we can't show you the difference here — this is what the help text says
Narrow the task instead of the approvals
If there's no permission layer, the remaining levers limit reach:
--add-dir— for the workspace--agent/--agent-file— to run under a profile with a tighter remit--skills-dir— to control which skills load
So if you were to use this tool, it would be better to aim narrow rather than permissionless
What we ran. Kimi Code CLI 0.31.1 — the new version of the tool, not the old Python kimi-cli package: a native binary under ~/.kimi-code/bin, its own config directory, and a migrate subcommand for importing the previous installation. Two kimi -p runs in an empty git repository under /tmp, no flags, stdin closed. The flag wording is quoted from kimi --help
If you automate it anyway
If you decide to go with automation:
Check the effect, not the exit code — after the run, make sure there's a file, a PR is open, there's a label. Five of six runs exited 0, and three of those didn't do anything
Read what the block tells you — every tool that blocked us told us so; in an array, on a line, in a banner. Only cursor-agent encoded it in the exit code, and that was a trust gate rather than a block
Limit the run, assign an owner — time limit, budget limit, person to get the failure report. An unattended run without an owner is a subscription, not automation
An unattended run is a policy that's being written before you know what the question is. Automate the things where you can write this policy honestly, and otherwise stop at rung 2.