claude-print
Health Uyari
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Drop-in replacement for claude -p that drives the interactive TUI via PTY, preserving subscription billing
claude-print
Drop-in replacement for claude -p (print/headless mode) that drives the Claude Code interactive TUI via PTY — keeping sessions on the unlimited subscription pool rather than the per-token Agent SDK credit pool.
Why this exists
Anthropic routes claude -p (headless/SDK mode) through a separate Agent SDK credit pool ($100–$200/month on Max plans). Only the interactive TUI (cc_entrypoint=cli) draws from the unlimited subscription.
The billing path is determined by an isatty check inside the claude binary: when stdout is a TTY, the session is tagged cc_entrypoint=cli and billed against the subscription. When stdout is a pipe (as with claude -p), it becomes cc_entrypoint=sdk-cli and draws from the credit pool instead.
claude-print allocates a PTY, drives the interactive TUI over it, auto-dismisses the trust dialog, injects the user prompt via bracketed paste, waits for the Stop hook via a FIFO, reads the JSONL transcript, and emits clean stdout output — giving callers claude -p wire-compatible output while billing against the subscription.
Prerequisites
- Claude Code must be installed and authenticated. See claude.ai/code.
- An active Claude subscription (Pro or Max plan) is required. The whole point is to bill against subscription, not credits.
HOMEmust be set to a non-empty value for the user runningclaude-print. Claude Code configuration, trust state, and transcripts are resolved beneath this directory.- Linux only. PTY support requires POSIX — no Windows ConPTY.
Install
sh install.sh
install.sh downloads a pre-built static musl binary from GitHub Releases (jedarden/claude-print), runs --check to verify the setup, and copies claude-print.yaml to ~/.needle/agents/ if NEEDLE is present.
Set SKIP_MOCK_CLAUDE=1 to skip the mock_claude test fixture download.
Build from source
git clone https://github.com/jedarden/claude-print
cd claude-print
cargo build --release
# binary at target/release/claude-print
# fully static binary (recommended for deployment):
cargo build --target x86_64-unknown-linux-musl --release
Architectures: x86_64 only (static musl binary). aarch64 / ARM Linux is out of scope for v1.0 — see docs/plan/plan.md Non-Goals. CI builds only for the x86_64 runner; an install.sh aarch64 branch would 404 because no such release asset is produced.
Self-check
After install, verify the PTY, Stop hook, and billing entrypoint:
claude-print --check
This confirms cc_entrypoint=cli appears in the session JSONL. install.sh runs this automatically, but it's worth running manually after upgrades.
Usage
claude-print [OPTIONS] [PROMPT]
Reads the prompt from a positional argument, --input-file, or stdin (when not a TTY). These are mutually exclusive.
Examples
# Positional prompt
claude-print "Summarize this in one sentence"
# Stdin pipe
echo "what is the capital of France?" | claude-print
# File input
claude-print --input-file prompt.txt
# Specify a model
claude-print --model claude-opus-4-8 "Write a haiku about Rust"
# JSON output
claude-print --output-format json "what is 2+2?" | jq .result
# Stream-JSON — real-time JSONL event replay
claude-print --output-format stream-json "Write a story"
# Agentic task with tool use
claude-print --max-turns 5 "List files in current dir and summarize"
# Short timeout for quick questions
claude-print --timeout 30 "quick question"
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
[PROMPT] |
Prompt string (mutually exclusive with --input-file and stdin) |
||
--input-file <FILE> |
-f |
Read prompt from file | |
--model <MODEL> |
-m |
claude-sonnet-4-6 |
Model to use |
--max-turns <N> |
30 |
Maximum agentic turns | |
--output-format <FORMAT> |
-o |
text |
Output format: text, json, stream-json |
--allowedTools <LIST> |
Comma-separated list of allowed tools | ||
--disallowedTools <LIST> |
Comma-separated list of disallowed tools | ||
--dangerously-skip-permissions |
Skip permission prompts (dangerous) | ||
--timeout <SECS> |
3600 |
Wall-clock timeout in seconds | |
--first-output-timeout <SECS> |
90 |
First-output timeout in seconds (PTY output) | |
--stream-json-timeout <SECS> |
90 |
Stream-json first-output timeout in seconds | |
--stop-hook-timeout <SECS> |
120 |
Stop hook watchdog timeout in seconds | |
--claude-binary <PATH> |
PATH lookup | Path to claude binary | |
--config <FILE> |
XDG or user config | Read configuration from an explicit TOML file | |
--no-inherit-hooks |
Disable user hook inheritance | ||
--verbose |
Write timing traces to stderr | ||
--check |
Run installation self-test and exit | ||
--clean |
With --check, remove orphaned temp directories older than one hour |
||
--version |
-V |
Print version and exit | |
--help |
-h |
Print help |
Configuration
By default, claude-print reads $XDG_CONFIG_HOME/claude-print/config.toml
when XDG_CONFIG_HOME is set, or ~/.config/claude-print/config.toml
otherwise. Use --config <FILE> to select another file. The file is optional;
when it is missing, the built-in defaults are used.
Configuration uses TOML. Every key is optional, so partial configurations are
valid:
[defaults]
model = "claude-sonnet-4-6"
inherit_hooks = true
max_turns = 30
timeout_secs = 3600
model must start with claude- and may contain letters, numbers, hyphens,
underscores, and dots. max_turns must be from 1 through 1000, andtimeout_secs must be from 1 through 86400. Unknown keys, invalid value types,
out-of-range values, and malformed TOML are rejected.
Only a missing file falls back to defaults. If an existing config cannot be
read, parsed, or validated, claude-print exits with status 2 instead of
continuing with a warning. In json and stream-json modes, a configuration
failure leaves stdout empty and writes a structured result to stderr, for
example:
{"claude_version":"2.1.238 (Claude Code)","error_message":"invalid config: /tmp/config.toml: TOML parse error ...","is_error":true,"subtype":"internal_error","type":"result"}
For a quick check without touching the default config:
bad_config="$(mktemp)"
printf '[[\n' > "$bad_config"
claude-print --config "$bad_config" --output-format json "test prompt" 2>config-error.json
status=$? # 2
jq . config-error.json
rm "$bad_config" config-error.json
Output formats
text(default): plain text response, printed to stdout.json: one-line JSON object withtype,subtype,is_error,result,session_id,num_turns,duration_ms,cost_usd,claude_version, andusagefields.resultholds the response text (there is notextormodelfield);usageis an object withinput_tokens,output_tokens,cache_creation_input_tokens, andcache_read_input_tokens.stream-json: JSONL replay of the raw transcript events in real time, one event per line.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Assistant error (is_error: true in transcript) |
2 |
Internal error (PTY spawn, hook setup, parse failure) |
4 |
Input error (no prompt provided, or --input-file/stdin unreadable) |
124 |
Timeout exceeded |
130 |
Interrupted (SIGINT) |
How it works
- PTY fork — spawns
claudeunder a PTY soisattyreturns true and the session is taggedcc_entrypoint=cli. - Trust dialog dismiss — watches for the one-time "do you trust this project?" prompt and sends the confirmation keypress automatically.
- Bracketed paste injection — sends the prompt wrapped in bracketed-paste escape sequences (
\x1b[200~/\x1b[201~), which Claude Code's TUI accepts as user input without triggering shell interpretation. - Stop hook FIFO — installs a temporary Claude Code Stop hook that writes a payload to a FIFO when the response is complete; the process blocks on the FIFO read.
- Transcript read — reads the JSONL session transcript Claude writes to
~/.claude/projects/, extracts the assistant turn, and emits it in the requested format.
NEEDLE integration
If you use NEEDLE for LLM fleet dispatch, install.sh automatically copies claude-print.yaml to ~/.needle/agents/. This registers claude-print as the adapter for Anthropic subscription models (sonnet/opus/haiku) so NEEDLE workers bill against the subscription rather than the Agent SDK credit pool. See claude-print.yaml in the repo root for the full adapter config, including --no-inherit-hooks isolation mode and the use_or_lose cost type.
Limitations
- Linux only — PTY allocation is POSIX. No Windows ConPTY support.
- Claude Code must be authenticated —
claude-printdelegates entirely to theclaudebinary; it cannot authenticate on its own. - One prompt per invocation — there is no multi-turn session mode; each call starts a fresh session.
- Startup latency ~2–5s — the PTY handshake and Claude Code startup add overhead versus a direct HTTP call.
Troubleshooting
HOME in containers and chroots
Running a session, --check, or --version requires HOME to contain the
current user's home directory. claude-print deliberately does not guess/root or consult the passwd database: choosing the wrong directory could read
or write another user's Claude Code configuration and transcripts.
If HOME is unset or empty, text output exits with status 2 and reports:
error: invalid config: HOME environment variable not set or empty; set HOME to the user's home directory
The json and stream-json output formats also exit with status 2 and put the
same message in the result object's error_message field. SettingXDG_CONFIG_HOME does not remove the HOME requirement: it can relocateclaude-print's config file, but Claude Code state and transcripts still live
under $HOME.
In a container, chroot, or service unit, set HOME explicitly to the home of
the account that runs claude-print. Create that directory with the correct
ownership and mount or provision the user's authenticated Claude Code state
there. For example:
ENV HOME=/home/claude
# Kubernetes container specification
env:
- name: HOME
value: /home/claude
For a chroot or one-off service invocation, the equivalent isHOME=/home/service claude-print "...". Do not use /root unless the process
actually runs as root and /root is intentionally where its Claude Code state
is stored. Startup verifies that the configured path exists, is a directory,
and permits a temporary file to be created and written. This detects missing
home mounts, permission problems, and read-only filesystems before Claude Code
starts. The probe file is removed immediately; failures name the configured
path and never fall back to /root.
Migration note: Older builds checked HOME only while resolving particular
paths, accepted an empty or unprovisioned value at some call sites, and could
run early-exit commands such as --version without it. Current builds enforce
one consistent, existing-and-writable HOME contract before session startup,--check, and --version. When upgrading existing containers or service
definitions, add an explicit HOME, create or mount it with write permission,
and update health checks that invoke --version in a stripped environment.
Argument-parser help (--help) is still rendered before runtime HOME validation.
Billing classification verification
Before deploying to production, verify that sessions are billing against the subscription pool (cc_entrypoint=cli):
# Check the most recent session's billing classification
./scripts/check-billing.sh
This script inspects the latest transcript JSONL under ~/.claude/projects/ and asserts the entrypoint field is "cli" (subscription), not "sdk-cli" (credit pool). Exit 0 means correct billing; exit 1 means a billing regression. Run this after every release or Claude Code upgrade.
Production hosts also run a daily credential-backed canary. Install its
systemd user timer on ex44 and lab with:
./scripts/install-billing-canary.sh
The canary makes a single one-turn Haiku invocation, checks that invocation's exact
transcript with check-billing.sh, and atomically writes PASS or FAIL to~/.local/state/claude-print/billing-canary/last-result. Seescripts/billing-canary.md for timer and alerting
details. The manual release check remains required as a second layer.
Common issues
PTY open failed — You may be in a container without /dev/ptmx. Run on a bare-metal host or a VM with full PTY support.
Session never completes — The Stop hook may not be firing. Check --verbose output for "Stop received" and verify your ~/.claude/settings.json isn't blocking hook execution.
Empty output despite success — The transcript reader may have hit a race condition. Run with --verbose to see retry attempts; if retries exceed 40×50ms, the Stop hook fired before the JSONL was flushed.
Release checklist
Before cutting a release tag:
- Run
./scripts/check-billing.shto verify billing conformance (requires credentials) - Run
cargo testto ensure all mocked tests pass - Run
claude-print --checkto verify PTY and Stop hook mechanics - Check Claude Code version currency: if the installed Claude Code version (
claude --version) has changed since the last release, capture a real session transcript and add it astests/fixtures/transcript_vX.Y.Z.jsonlwith corresponding regression tests intests/version_compat.rs - Update version in
Cargo.toml - Commit and push:
git tag v0.x.y && git push origin v0.x.y - Monitor the
claude-print-ciArgo Workflow for successful build and GitHub release
Structure
docs/notes/— design decisions, constraints, integration detailsdocs/plan/plan.md— complete implementation planscripts/check-billing.sh— AS-4 billing conformance script (run before every release)scripts/billing-canary.sh— daily credential-backed AS-4 canaryscripts/claude-print-billing-canary.{service,timer}— systemd user units for the canaryscripts/— integration test scripts
Part of jedarden.com
This GitHub repo is a read-only mirror of git.ardenone.com/jedarden/claude-print — issues and PRs are welcome here either way.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi