My AI Agent Invented a CLI Flag, and I Called the Error "Cosmetic"
A daily AI agent job kept failing with "unknown flag --readonly". The flag never existed. Why agents invent flags, and why --token=x beats --token x.
A scheduled job on the self-hosted AI assistant I run for a small business had been showing error as its last status for a long time. Every day. The job scans the company's cloud storage for new documents and notes what's changed. I had a theory: the agent did its work and then fumbled an edit tool call at the very end. Cosmetic. Not worth the time.
That theory was wrong. The real cause was one word the agent had made up.
TL;DR: The agent appended --readonly to a CLI call because it sounded right for a read-only job. The CLI has no such flag, answered unknown flag --readonly, the tool call failed, and the run was marked as an error. Fix: a guardrail in the job prompt ("no --readonly, read-only is how you behave, not a flag") plus the exact command spelled out, with --token=<value> written with an equals sign. Then a manual test run: lastRunStatus: ok.
The fix first
This is the kind of block I now put into every agent job prompt that calls a CLI. Paraphrased, with example values:
Check for changes with EXACTLY this command, nothing added, nothing removed:
gog --account alice@example.com drive changes list --token=<startPageToken from state.json> --json
Rules:
- There is NO --readonly flag. Do not add it. Read-only is a rule for your
behaviour (don't modify, move or delete anything), not a command-line option.
- Write the token as --token=<value> with an equals sign, never with a space.
- If the command fails, report the exact error text. Do not guess another syntax.
After that, one manual run of the job and a look at its status:
lastRunStatus: ok
The error field was gone. First clean run in a long while.
What actually happened
The job's instructions told the agent, quite reasonably, to be read-only: look at what changed in the cloud storage, take notes, don't touch anything. The CLI the agent uses for this is a general Google Workspace command-line tool.
The agent turned "be read-only" into a command-line flag. Somewhere in its reasoning, "read-only" plus "CLI call" became --readonly. Lots of tools have a flag like that, so it's a very plausible guess. This one doesn't.
unknown flag --readonly
The tool call failed, and the scheduler correctly recorded the run as error. Every single day.
Gotcha: "cosmetic" is a diagnosis, and it needs evidence
The worst part of this story is mine, not the agent's. I saw an error status, I spotted something that looked like a failed edit at the end, and I filed it under cosmetic without reading the actual error text.
That's how a daily error turns into background noise. Once you've decided a red status is harmless, you stop looking at it, and it sits there hiding the next real problem too.
What I should have done the first time, and what I do now:
- Open the run's actual error message, not just the status.
- Find the exact tool call that failed and the exact arguments it was given.
- Run that same command by hand.
Step 3 would have taken thirty seconds and printed unknown flag --readonly in my face.
Why agents invent flags
LLMs are trained on a mountain of CLI usage. They know what flags usually look like: --readonly, --dry-run, --quiet, --force, --json. When the model is unsure of a specific tool's syntax, it fills the gap with what's typical. Usually that's close enough. Sometimes it's a flag that only exists in a different tool, or in a version from two years ago, or nowhere at all.
The trigger in my case was a behaviour rule ("be read-only") that the model "helpfully" translated into syntax. That's a pattern worth watching for in general: if your prompt describes a constraint in words, the agent might try to enforce it with a flag, a parameter, or an option that doesn't exist.
Give exact commands, not descriptions
The fix that works best for me: don't describe what the command should do, write down the command.
| Prompt says | Agent does |
|---|---|
| "List the changes since the last run, read-only" | Builds its own command, may add flags it imagines |
"Run exactly: <command>" |
Runs that command |
It feels like over-specifying. It isn't. A scheduled job runs unattended every day; every degree of freedom you leave is a chance for a creative variation that breaks on day 23.
If the command needs a value from state (a token, an ID, a date), name exactly where it comes from: "the startPageToken from state.json", not "the token". The less the agent has to infer, the less it invents.
Gotcha: --token=x vs --token x
The second half of the guardrail looks like pedantry. It's not.
Most CLI parsers accept both --flag value and --flag=value. The problem is what happens when the value is missing or odd. With a space, the parser treats the next argument as the value, whatever it is. If the agent's placeholder for the token comes out empty, or gets split, the command becomes:
gog --account alice@example.com drive changes list --token --json
and the parser happily takes --json as the token. The CLI here is built on kong, a Go command-line parser, and that's the trap: with a space, the next flag can get swallowed as the value. At best you get a confusing error, at worst a request with a nonsense token and output without the format you asked for.
With an equals sign, the value is glued to its flag:
gog --account alice@example.com drive changes list --token= --json
Now an empty token is visibly empty, --json stays --json, and whatever goes wrong goes wrong in an obvious way. For anything an agent fills in, use =. It makes the boundaries explicit, for the parser and for you reading the logs.
Lessons I'm keeping
Agents invent plausible flags. Not maliciously, not often, but reliably enough that it will happen in any job that runs long enough. Plan for it the way you plan for a typo in a cron line.
Give exact commands. Copy-paste-ready, with placeholders that name their source. Describe behaviour separately and say explicitly that it's behaviour, not syntax.
Look at errors instead of classifying them. "Cosmetic" was a guess I never checked. A recurring error on a scheduled job is either a real problem or a monitoring bug, and both deserve ten minutes.
= for flag values in anything automated. It costs nothing and removes a whole class of parser surprises.
This is the same agent that taught me to separate curated memory from what it writes on its own, and earlier that the tools it shells out to belong in its container image. Unattended agents need the same discipline as any other automation, including making jobs safe to run twice.
FAQ
Can't I just let the agent read --help first? You can tell it to, and for interactive use that's a good habit. For a daily job I'd still rather hardcode the known-good command. Fewer steps, fewer tokens, fewer chances to improvise.
Should the CLI accept unknown flags silently? No. The unknown flag error did its job perfectly. The failure was that nobody, me included, read it.
Does a bigger model fix this? It lowers the odds, it doesn't remove them. Exact commands in the prompt work with any model.
How do I find these in my own setup? List your scheduled agent jobs, filter for any whose last status isn't ok, and read the actual error text of the last run. If you have one you've been calling cosmetic, start there.