# Using it in place of `claude`
A lot of CI jobs and shell scripts run an agent as `claude -p`. Codey reads the
same flags with the same meaning, so pointing one of them at Codey is meant to
be a matter of changing the binary's name:
```bash
codey -p --output-format json "why does TestFoo fail?" | jq -r .result
echo "summarise the last commit" | codey -p --allowedTools "Bash(git log:*)" "Bash(git show:*)"
codey -c -p "now fix it" # same session
```
| Claude Code flag | In Codey |
| --- | --- |
| `-p`, `--print` | One prompt, then exit. The prompt may follow `-p` — Codey's own spelling — or be a positional argument anywhere on the line, as `claude` takes it, or arrive on stdin when there is no other |
| `--output-format text` | The final answer and nothing else on stdout |
| `--output-format json` | One result object at the end |
| `--output-format stream-json` | One JSON line per message as the run goes, then the result object |
| `--verbose` | The same as `-v`. With `json` it prints every line `stream-json` would have, as one array, which is what Claude Code does; `stream-json` does not need it |
| `-c`, `--continue` | `-resume latest`: the most recent session in this working root |
| `--resume <id>` | Unchanged; the id is a Codey session id, which is what `session_id` in the output holds |
| `--model`, `--max-turns` | Codey's own flags, already spelled the same |
| `--allowedTools`, `--allowed-tools` | Calls to approve without asking, as rules |
| `--disallowedTools`, `--disallowed-tools` | Tools to take away, or calls to refuse, as rules |
| `--permission-mode` | `default`; `acceptEdits`, which lets edits and writes through and still asks before a command — the mode the ACP front end has by the same name; `bypassPermissions`, which is `-yolo`; `dontAsk`, which refuses whatever no rule allows |
| `--dangerously-skip-permissions` | `-yolo` |
| `--append-system-prompt`, `--system-prompt` | Add to the end of Codey's system prompt, or replace it. The session's own agent only: a subagent's prompt is its preset's |
| `--include-partial-messages` | Accepted, with a warning, and ignored |
Without `--output-format`, `-p` prints what it always has: the transcript as it
streams, the tool calls, a usage line — for a person watching, not a parser.
Asking for a format is asking for Claude Code's behaviour, and then stdout holds
the format and nothing else; notices, warnings and any approval prompt go to
stderr.
The shapes are Claude Code's, because the programs reading them were written
against it. The result object carries `type`, `subtype` (`success`,
`error_max_turns` or `error_during_execution`), `is_error`, `result`,
`session_id`, `num_turns`, `duration_ms`, `duration_api_ms`, `total_cost_usd`
and `usage` with the four token counts. In `stream-json` it is preceded by a
`system`/`init` line — working directory, model, tools, MCP servers, permission
mode — then an `assistant` line per response, holding the message as the
Messages API returned it, and a `user` line per batch of tool results. Numbers
are this invocation's: a resumed session reports what the resume cost, not
everything before it.
A run that fails exits 1, whatever the format, and the JSON formats still print
their result object — with `is_error: true`, the error in `errors`, and in
`result` whatever the model last said, or the error itself when it said
nothing. That includes failing before the model was ever asked: no credentials
gives a result object, with an empty `session_id` because there was no session
yet. Check `is_error` rather than `subtype`: it is the field that means
failure in both, where the subtypes for a failure are not guaranteed to agree
between the two. A flag
Codey does not know stops the run before any of this, with exit 2, and so does
a value it cannot use — `--permission-mode plan`, say — with exit 1 and a
message on stderr.
**Who gets asked.** Claude Code never prompts in `-p` mode, and with
`--output-format` neither does Codey when stdin is not a terminal: what no rule
allows is refused, and the model is told so and carries on. That is the case in
CI. At a terminal the prompt still comes up, on stderr. Codey's own `-p`
without a format keeps reading answers from stdin, so `yes | codey -p ...`
works as it always has — except when the prompt itself came from stdin, which
leaves nothing there to answer with.
**Rules.** `--allowedTools` and `--disallowedTools` take Claude Code's rule
syntax, comma- or space-separated, as many as you like after one flag:
`Bash`, `Bash(git status)`, `Bash(npm run test:*)`, `Read`, `Read(./src/**)`,
`Edit(./docs/**)`, `Write`, `Glob`, `Grep`, `WebFetch(domain:example.com)`,
`WebSearch`, `TodoWrite`, `Task`, `mcp__github`, `mcp__github__create_issue`.
Claude Code's names map onto Codey's tools — `Bash` is `bash`, `Read`
`read_file`, `Write` `write_file`, and `Edit` both `edit_file` and
`write_file`, because Claude Code applies its Edit rules to every tool that
edits files. Codey's own names work as well. A rule naming a tool Codey does
not have, `NotebookEdit` say, is a warning, and the rest of the list still
applies.
A rule for a whole tool on the disallowed list takes the tool away: the model
is never shown it. A narrower one — `Bash(git push:*)`, `Read(./.env)` — is
checked at approval time, before anything else, `-yolo` included, and a call it
matches is refused. On the allowed list a rule stands in for the "yes" you
would otherwise have been asked for.
`Bash(prefix:*)` matches word by word, so `Bash(ls:*)` covers `ls -la` and not
`lsof`. A compound command is allowed only when every simple command in it is
covered, so `git status; rm -rf ~` does not ride in on `Bash(git status:*)`, and
anything the [shell analysis](safety.md) cannot see through — command
substitution, a variable in command position, an inline `bash -c` script, an
assignment like `PATH=...`, output redirected into a file — is never allowed by
a narrower rule than `Bash`. A deny rule is the other way round: one matching
part is enough, and it looks inside `bash -c` and `eval`, past `sudo`, `env`
and `timeout`, and at `/bin/rm` as `rm`. That catches the command a model
would ordinarily write. It is not a sandbox, for the reason the Safety section
gives.
Path rules read like gitignore, as Claude Code's do: `./` and `/` are the
working root, `//` an absolute path, `~/` your home, and a pattern with no
slash in it — `.env`, `*.pem` — matches a name at any depth. Path rules on
`Read` apply to `glob` and `grep` too, as far as their `path` argument goes.
What does not work:
- `--permission-mode plan`. Codey has no read-only planning mode, and says so
rather than run a mode it does not have. Ask for a plan in the prompt, and
`--disallowedTools Edit,Write,Bash` holds the run to it.
- `--input-format stream-json`, and so a conversation fed in over stdin. The
prompt is text.
- `--include-partial-messages`: there are no `stream_event` lines, only whole
messages.
- `--mcp-config` and `--strict-mcp-config`. MCP servers come from
[Codey's config](mcp.md).
- `--settings`, `--setting-sources`, `--add-dir`, `--fallback-model`,
`--session-id`, `--fork-session`, and `--resume` with no id, which in Claude
Code opens a picker.
- Piping a file in alongside a prompt. `cat log | claude -p "explain"` hands
Claude Code both; Codey reads stdin only when there is no prompt otherwise,
because a CI runner can leave stdin as a pipe nobody ever closes, and a run
that waited on it would hang there for good. Put it in the prompt:
`codey -p "explain: $(cat log)"`.
- A prompt without `-p`, which in Claude Code starts an interactive session
with that prompt. Codey ignores it and says so.
- Tool names in the output are Codey's — `bash`, `read_file` — not Claude
Code's. A script that looks for `"name":"Bash"` in the stream needs to look
for `bash`.
- The result object has no `permission_denials` or `modelUsage`, and lines
carry no `parent_tool_use_id` other than `null`: subagents report through a
tool result rather than in the parent's stream.