?

When to use claudecli

claudecli shells out to the claude CLI (Claude Code) as a subprocess. It is the default provider and the right choice when:

  • You already have Claude Code installed and authenticated locally.
  • You want to reuse a subscription-tier Claude Code account rather than plumb API keys.
  • You want the model to run inside claude's own tool-use loop (its file-editing, thinking, and plan mode features are intact).
  • You want zero secret material in rousseau's config file.

The trade-off: rousseau's tool Registry is not invoked for this provider — claude runs its own tools inside the subprocess. Response objects come back as a single end-of-turn text message. If you need rousseau to gate bash/edit/write through the approval policy, use anthropic, bedrock, vertex, or an OpenAI-compatible provider instead.

Auth inheritance

The claude CLI holds authentication in three places:

Location Contents
~/.claude/ OAuth tokens (subscription), API-key helper output, workspace config.
System keychain On macOS, claude may cache refresh tokens in the login keychain.
ANTHROPIC_API_KEY env If set, claude uses it for API-key mode instead of OAuth.

claudecli never reads these directly. Every invocation is exec.CommandContext(binary, args...) — the subprocess inherits the parent's environment and home directory, and looks up its own credentials. That is what makes it "zero-config" for individual operators.

Volume=%h/.claude:/home/rousseau/.claude:rw,Z

The Z label is critical on SELinux hosts; see Deployment for the full Quadlet unit.

Configuration

provider: claudecli

claudecli:
  binary: claude
  model: sonnet
  permission_mode: bypassPermissions
  extra_args:
    - --add-dir
    - /workspace
Field Default Effect
binary claude Executable resolved on $PATH. Point at an absolute path if you have multiple claude versions.
model empty Passed as --model <value>. Empty uses claude's default.
permission_mode empty Passed as --permission-mode <value>. See table below.
extra_args [] Prepended before -p <prompt> on every invocation.

Every field maps to ClaudeCLIConfig in internal/config/config.go. The subprocess command line assembled at each turn is:

claude --print --output-format json \
  --session-id <sessionID> \
  --system-prompt <systemPrompt> \
  --model <model> \
  --permission-mode <permissionMode> \
  <extra_args...> \
  <prompt>

PermissionMode matrix

The PermissionMode flag mirrors claude's own --permission-mode. The subprocess enforces the value; rousseau does not double-check.

Interactive TUI sessions where a human sits at the terminal and can approve tool calls.

Mode Behaviour
default Claude Code prompts interactively for every tool call. Best for exploratory sessions.
acceptEdits File edits proceed without prompting; other tools still prompt. Good when you trust the edit surface.
auto Automatic based on the tool. Use when you want claude's built-in heuristic to decide.
claudecli:
  permission_mode: acceptEdits

Session correlation

claudecli maintains conversation state inside the subprocess. Rousseau correlates its own session IDs with claude's via two flags:

  • claude -p --session-id <uuid> creates a new session. If the UUID already exists, claude errors with already in use.
  • claude -p --resume <uuid> resumes an existing session. If unknown, claude errors.

Rousseau picks the flag using an in-memory SessionCache (InMemorySessionCache by default). On a cold-start cache miss where claude already has state from a previous rousseau run, the provider optimistically tries --session-id, catches the already in use error, and retries with --resume. See the comment on (*Provider).Complete in internal/llm/claudecli/client.go.

Callers embedding the provider can swap in a persistent cache via provider.WithCache(store) — the state.sqlite store implements the same interface and survives daemon restarts, avoiding the cold-start roundtrip on the first turn after a reboot.

Model aliases

claude's model aliases are honoured by the subprocess unchanged:

Alias Points at
sonnet The current default Sonnet-tier model.
opus The current default Opus-tier model.
haiku The current default Haiku-tier model.

For reproducibility across daemon restarts (skill benchmarks, cron jobs, batch runs), pin an exact model ID:

claudecli:
  model: claude-sonnet-4-6

Combining with skills

claudecli sends the system prompt via --system-prompt on session creation. claude honours it verbatim and ignores subsequent --system-prompt values on --resume — which matches how rousseau uses it. The SkillsProvider output is spliced in before the invocation:

<agent.SystemPrompt>

<skill 1 markdown>

<skill 2 markdown>

<RecallProvider appendix>

See internal/agent/agent.go systemPrompt(). Skills work identically across every provider; the mechanics of the composition happen in agent.Agent, not the provider.

Gotchas

  • No cross-provider portability. A session created against claudecli is not portable to anthropic — model-side state lives inside claude. Switching providers midway forces a new session.
  • Tool registry is not invoked. bash, edit, write, grep, read are executed by claude, not by rousseau. Rousseau's agent.Approver cannot gate those calls. Use a non-claudecli provider if you need rousseau-side approval enforcement.
  • --add-dir scoping. By default claude refuses to read outside its own workspace. Pass --add-dir /workspace (or wherever your source lives) via extra_args to widen it. Combine with rousseau's approval policy at the transport level if you want to compensate for the loss of control.
  • Streaming. claudecli uses claude -p --output-format json (non-streaming). The streaming path in internal/llm/claudecli/stream.go reads --output-format stream-json; opt in by using StreamingProvider from an embedding integration.
  • Environment leakage. The subprocess inherits every environment variable of the parent. If ANTHROPIC_API_KEY is set in rousseau's environment, claude will prefer it over cached OAuth. That is usually fine, but it changes billing.

Troubleshooting

claudecli: run: exec: "claude": executable file not found in $PATH

claude is not on PATH (or the container image does not ship it). Two fixes:

  1. Set claudecli.binary to an absolute path.
  2. Add Claude Code to the container's runtime layer — the reference docker/Dockerfile uses node:22-alpine for this reason.

claudecli: model error: session id already in use

You are running two rousseau processes against the same session ID against the same claude install, or the in-memory cache dropped a session that claude still remembers. The optimistic retry described above handles the second case; the first means you have concurrent daemons stepping on each other.

claudecli: no JSON in output

claude printed non-JSON to stdout, or exited before emitting the envelope. Common causes: an invalid API key on the Claude Code side, a claude version that predates --output-format json, or a shell wrapper writing progress markers. Run claude -p --output-format json 'hello' directly to isolate.

The reply cuts off mid-sentence

claude's output is capped by --max-turns and its own internal token budget. Rousseau does not set --max-turns; if you set it via extra_args, raise it. For long generations, consider a direct API provider where you control MaxTokens from internal/llm/anthropic/client.go.

Subscription plan is rate-limited but the API is fine

The claude CLI on a subscription plan has hidden per-conversation and per-window limits. If you hit them, switch to provider: anthropic with an API key — the direct API has explicit, published limits (see Guides: Rate limits).

Related pages

Further reading

  • internal/llm/claudecli/client.go — subprocess invocation, session correlation, JSON parsing.
  • internal/llm/claudecli/stream.go — streaming variant using --output-format stream-json.
  • internal/config/config.goClaudeCLIConfig struct.
  • internal/cli/root.go — how setUnattendedPermissionDefault picks bypassPermissions for chat transports.

Type to search 150+ pages. Ranking: BM25 with title/description boost.