Run Claude Code headless (claude -p) from Python on your subscription, with no API key.
It builds the flags, isolates the call, sorts the errors and parses the answer. Calls go through the OmniRoute gateway, which picks the route. Six of my projects had their own copy of this code, and this library replaced them.
Standard library only, Python 3.12+.
uv add claude-subprocess # or: pip install claude-subprocessThe PyPI name is claude-subprocess, the import name is claude_cli.
At runtime you need omniroute and claude on PATH, with Claude Code logged in.
from claude_cli import run_claude, extract_json
res = run_claude('Reply with {"ok": true}', model="haiku", effort="low", timeout=60)
res.text # the model's answer
res.cost_usd # cost the envelope reported (may be None)
res.duration_ms
res.raw # the full envelope dict
extract_json(res.text) # {'ok': True}extract_json tolerates code fences and prose around the object:
>>> extract_json('Sure! Here it is:\n```json\n{"ok": true}\n```\nAnything else?')
{'ok': True}Async, streaming, cache, batch, project mode and pre-flight checks: docs/usage.md.
Tool-less calls go over HTTP to the gateway. Calls that need tools, a project cwd or stream-json go through the CLI.
If HTTP fails, the call falls back to the CLI and HTTP is paused for 600 s.
Rules, switches and env vars: docs/routing.md. Step by step: docs/logic-map.md.
- Claude Agent SDK (
claude-agent-sdk) is the official way to drive Claude Code from code, with the full agent loop. Use it to build an agent or a product. Its docs ask third-party products to use API keys, not a claude.ai login. - This library is for your own scripts on your own subscription: one prompt in, text or JSON out, no dependencies.
anthropicis the Messages API, billed per token on anANTHROPIC_API_KEY. Use it when you have a key and want every API feature.subprocess.run(["claude", "-p", ...])is what this library replaced. The hard parts are the isolation flags, killing the process tree on timeout, telling a usage limit from a login problem, and parsing the envelope.
These are the reason the library exists.
--bareis never passed. It forces API-key-only mode and breaks subscription auth. A test checks it.- Every Claude Code subprocess is launched via
omniroute run claude, and the HTTP route talks to the same gateway. - The surface is isolated: an empty temp MCP config with
--strict-mcp-config,--setting-sources user,--disable-slash-commands,--no-session-persistence. - Every call gets a throwaway cwd, so no project
CLAUDE.mdgets loaded. --tools ""is the default. A non-emptytoolsvalue goes to both--toolsand--allowed-tools, because headless-pdenies anything not allow-listed.--output-format jsonandstream-jsonboth end up as the sameClaudeResult.
All errors derive from ClaudeCliError(RuntimeError).
NotLoggedInError and UsageLimitError fail fast, so a batch stops at the first one.
Causes and gateway diagnosis: docs/routing.md.
uv run --extra dev pytest -qThe suite fakes the subprocess calls and never runs the real claude binary.
MIT. Contributions: CONTRIBUTING.md.
