Claude Code CLI provider
Route chat workloads through the Claude Code CLI.
OpenHuman can send any chat workload through Anthropic's claude CLI instead of calling the Anthropic HTTP API directly. The CLI handles model selection, auth and prompt caching. OpenHuman runs it as a long-lived child process that resumes sessions, parses its stream-json output, and gives it an MCP endpoint so the model can reach OpenHuman state (memory, threads, agents, search). This is one of several pluggable LLM backends. See Engines for the full list.
tinyagents-harness (vendor/tinyagents) owns the provider, ClaudeCodeProvider. See its README for the file map and implementation notes. OpenHuman only wires it up in crates/openhuman-core/src/inference/provider/factory/subprocess_providers.rs, which supplies the MCP endpoint (OpenHumanMcpEndpoint, below) and reads the resulting model string back into its own routing.
Requirements
Claude Code CLI 2.0.0 or newer (
MIN_CLI_VERSION). It can be onPATH, in a supported well-known install location, or selected withOPENHUMAN_CLAUDE_CLI=/abs/path/to/claude.An Anthropic API key in
ANTHROPIC_API_KEY, or an existing~/.claude/.credentials.jsonfromclaude login.The
http-serverCargo feature (on by default). OpenHuman exposes its MCP tools to the CLI over a loopback HTTP endpoint (crate::mcp::server::ensure_local_http), not by runningopenhuman-core mcpas a subprocess. Without that feature the provider still runs, but the CLI gets no OpenHuman tools.
Routing a workload through the CLI
Use the provider prefix claude-code:<model>[@<temperature>]. Set it per role through the standard inference settings:
# Through the JSON-RPC update endpoint:
openhuman-core rpc openhuman.inference_update_model_settings \
--json '{"chat_provider":"claude-code:claude-sonnet-4-5"}'chat_provider
foreground chat replies
reasoning_provider
long-context reasoning workloads
agentic_provider
multi-step agentic loops
A workload set to claude-code:<model> starts a fresh claude child for every turn. Concurrency is capped at MAX_CONCURRENT_TURNS = 4 per ClaudeCodeProvider instance (see Per-turn behavior).
Verifying the install
The status RPC is in the inference namespace:
It returns one of these (CliStatus in tinyagents-harness's claude_code/types.rs):
{"status":"ok","version":"2.0.4","path":"/usr/local/bin/claude"}: ready{"status":"not_installed"}: no usableclaudewas found through the configured override,PATH, or the supported fallback locations{"status":"outdated","version":"1.9.0","min_required":"2.0.0","path":"..."}: upgrade the CLI{"status":"unusable","path":"...","reason":"..."}: binary present but the version probe failed
Binary lookup checks OPENHUMAN_CLAUDE_CLI first, then PATH, then the ordered fallback locations. The fallbacks cover the native installer and common user-local, Bun, npm-global and Homebrew installs. When the CLI is found through a fallback, its directory and the user bin directories are prepended to the child process PATH. The inherited entries stay available.
The settings panel shows the same status through ClaudeCodeStatusCard (app/src/components/settings/panels/ai/ClaudeCodeStatusCard.tsx).
Per-turn behavior
ClaudeCodeProvider::run_chat takes one of MAX_CONCURRENT_TURNS (4) semaphore permits, plus a per-thread mutex so two overlapping calls for the same conversation cannot race on one session UUID. Each turn then:
Resolves a per-thread session UUID from
<workspace_dir>/claude-code-sessions.json. New threads get a fresh RFC 4122 v4 UUID, because the CLI requires v4 for--resume.Asks the host's
McpEndpointProviderfor an MCP endpoint. OpenHuman's implementation (OpenHumanMcpEndpoint) lazily starts one in-process HTTP MCP server per core (crate::mcp::server::ensure_local_http, loopback only) and returns its URL plus a bearer token. The config file passed with--mcp-configcarries that token in itsAuthorizationheader. If the endpoint fails to start, the turn still runs, without OpenHuman tools.Spawns the CLI with:
-p --input-format stream-json --output-format stream-json --verbose --include-partial-messages --add-dir <project_dir>--mcp-config <scratch>/openhuman-mcp-config.json --strict-mcp-configwhen the endpoint resolved, so only the configured MCP servers are visible--permission-mode acceptEdits(default) orbypassPermissions(full access, described below)by default,
--disallowedTools Bash,BashOutput,KillShell,WebFetch,WebSearch,Task, so Claude Code's own shell, network and sub-agent builtins stay off and OpenHuman tools (mcp__openhuman__*) are the only way to reach those capabilities. This flag is omitted when full access is on--session-id <uuid>on the first turn,--resume <uuid>after that--model <model>(the suffix afterclaude-code:)--append-system-prompt-file <scratch>/append-system-prompt.txtif the conversation carries a system message. It is a file, not an argument, so a large harness prompt does not hit Windows' argument length limit
Pipes stdin. A new session gets the full conversation history folded into a text preamble. A
--resumegets only the pending user turns, because the CLI already holds the earlier context.Streams stdout through the JSONL parser, then the event mapper, into
ProviderDeltas on the request'sstreamsink.
If the CLI exits non-zero, the driver returns its stderr (capped at 16 KiB) as the error message.
On macOS the spawn runs inside a Seatbelt jail (sandbox-exec) by default when /usr/bin/sandbox-exec exists. Set OPENHUMAN_CLAUDE_CODE_SANDBOX=0 to opt out. The profile blocks reads and writes under the first .openhuman*-named ancestor of workspace_dir, which holds this provider's own session store and settings. It does not restrict the CLI's own file tools. Linux and Windows have no OS-level wall yet.
Permission posture and full access
The default, acceptEdits, limits Claude Code to file reads and edits under project_dir and withholds shell, network and Task access through --disallowedTools. You can opt into full access (the whole Claude Code toolset, including Bash) with the settings toggle saved in <workspace_dir>/claude_code_settings.json, or with OPENHUMAN_CLAUDE_CODE_PERMISSION_MODE=bypass|bypassPermissions|full. This is an explicit choice. Enabling the provider alone never grants shell or network access.
Auth resolution order
ANTHROPIC_API_KEYenv var (highest precedence, set on the spawned child).~/.claude/.credentials.json: the CLI's own login fromclaude login(Pro or Max subscription). OpenHuman leavesANTHROPIC_API_KEYunset on the child, so the CLI reads its own credentials. On macOS they live in the Keychain under theClaude Code-credentialsservice.Neither: the CLI fails with an auth error.
The openhuman.inference_claude_code_auth_status RPC reports the richer state for the Settings > AI panel. It runs claude auth status --json with a 10-second timeout instead of reading the credentials file, because a macOS login lives in the Keychain, not on disk.
Tool surface exposed to the CLI
The CLI sees these tools as mcp__openhuman__<name>, served over the loopback HTTP MCP endpoint described above. It is the same tool set that the stdio MCP server in crates/openhuman-core/src/mcp/server/ exposes to other MCP clients:
core.list_tools,core.tool_instructionsmemory.recall,memory.fetch,memory.list,memory.learn,memory.forgetagent.list_subagents,agent.run_subagent(write, flaggeddestructiveHintas the MCP spec requires)searxng_search
The MCP server enforces SecurityPolicy::ToolOperation checks. agent.run_subagent, memory.learn and memory.forget write. The rest are read-only. The CLI's own tool_use blocks (its internal Read, Bash and similar calls) never reach OpenHuman as harness tool calls, because the CLI has already run them by the time they appear in the stream. event_mapper.rs only strips their argument JSON from the visible text.
Limitations
Vision input is forwarded as native image blocks when pasted images are available to the Claude Code provider. Images that cannot be read are sent as a short text notice.
Every role routed to
claude-code:shares the sameMAX_CONCURRENT_TURNSsemaphore; under load a turn waits in the queue instead of failing fast.Cost accounting from the CLI's
result.total_cost_usdis captured in the mapper but not yet wired into OpenHuman's cost tracking (crates/openhuman-core/src/platform/cost/).Linux and Windows run the CLI unconfined; the Seatbelt jail is macOS-only.
Last updated