claudeA 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:
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 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.--settings, --setting-sources, --add-dir, --fallback-model,
--session-id, --fork-session, and --resume with no id, which in Claude
Code opens a picker.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)".-p, which in Claude Code starts an interactive session
with that prompt. Codey ignores it and says so.bash, read_file — not Claude
Code's. A script that looks for "name":"Bash" in the stream needs to look
for bash.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.