Drop-in replacement for claude -p that drives the interactive TUI via PTY, preserving subscription billing
Find a file
jedarden 1d0799531c chore(bf-3k2): remove repo cruft
- Remove notes/bf-1ae5.md (per-bead scratch file, not in Module Layout)
- Add .gitignore to exclude:
  - target/ (Rust build artifacts)
  - .beads/beads.db.backup.* (database backups)
  - .needle-predispatch-sha (NEEDLE worker artifact)

Root now contains only files specified in plan Module Layout:
AGENTS.md, build.rs, CI YAMLs, docs/, scripts/, src/, tests/,
Cargo.toml/lock, claude-print.yaml, install.sh, README.md
2026-07-02 14:59:36 -04:00
.beads docs(bf-1ae5): document local build of claude-print binary 2026-07-02 14:55:34 -04:00
.claude/worktrees chore(bf-2w7): verify cleanup implementation is complete and remove unused imports 2026-06-25 09:32:01 -04:00
docs docs(bf-4q2): update plan.md - stale Status table, module layout, and undocumented watchdog/flags 2026-07-02 14:42:48 -04:00
scripts feat(bf-1n6): add scripts/check-billing.sh for AS-4 billing conformance 2026-07-02 14:42:48 -04:00
src docs(bf-3wya): verify transcript byte offset capture implementation 2026-07-02 09:45:58 -04:00
test-fixtures/mock-claude fix(bf-3eq): add --version flag to mock-claude for watchdog test support 2026-06-25 17:57:20 -04:00
tests docs(bf-3wya): verify transcript byte offset capture implementation 2026-07-02 09:45:58 -04:00
.force_local docs(bf-3wya): verify transcript byte offset capture implementation 2026-07-02 09:45:58 -04:00
.gitignore chore(bf-3k2): remove repo cruft 2026-07-02 14:59:36 -04:00
AGENTS.md docs: update AGENTS.md — add session.rs to module map, document FIFO invariants and timeout-thread detach 2026-06-13 23:32:50 -04:00
build.rs fix(bf-2f5): correct timeout exit code from 3 to 124 2026-06-25 08:33:00 -04:00
Cargo.lock fix(bf-2w7): ensure temp dir and FIFO cleanup on all exit paths 2026-06-25 00:25:17 -04:00
Cargo.toml feat: forward --dangerously-skip-permissions, --allowedTools, --disallowedTools to child; bump v0.2.0 2026-06-14 18:11:53 -04:00
claude-print-ci-sensor.yml fix(bf-2f5): correct timeout exit code from 3 to 124 2026-06-25 08:33:00 -04:00
claude-print-ci-workflowtemplate.yml test(bf-3eq): add cargo test to CI workflow 2026-06-25 01:40:53 -04:00
claude-print-eventsource-stanza.yml fix(bf-2f5): correct timeout exit code from 3 to 124 2026-06-25 08:33:00 -04:00
claude-print.yaml Add Phase 9: NEEDLE integration — install.sh, claude-print.yaml, --check subcommand 2026-06-10 01:36:28 -04:00
install.sh fix(install): use x86_64-linux asset names instead of musl target suffix 2026-06-14 00:10:05 -04:00
README.md feat(bf-1n6): add scripts/check-billing.sh for AS-4 billing conformance 2026-07-02 14:42:48 -04:00

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.
  • 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 and aarch64.

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 .text

# 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
--no-inherit-hooks Disable user hook inheritance
--verbose Write timing traces to stderr
--check Run installation self-test and exit
--version -V Print version and exit
--help -h Print help

Output formats

  • text (default): plain text response, printed to stdout.
  • json: one-line JSON object with text, session_id, model, and usage fields.
  • 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)
124 Timeout exceeded
130 Interrupted (SIGINT)

How it works

  1. PTY fork — spawns claude under a PTY so isatty returns true and the session is tagged cc_entrypoint=cli.
  2. Trust dialog dismiss — watches for the one-time "do you trust this project?" prompt and sends the confirmation keypress automatically.
  3. 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.
  4. 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.
  5. 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 authenticatedclaude-print delegates entirely to the claude binary; 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 ~25s — the PTY handshake and Claude Code startup add overhead versus a direct HTTP call.

Troubleshooting

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.

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:

  1. Run ./scripts/check-billing.sh to verify billing conformance (requires credentials)
  2. Run cargo test to ensure all mocked tests pass
  3. Run claude-print --check to verify PTY and Stop hook mechanics
  4. Update version in Cargo.toml
  5. Commit and push: git tag v0.x.y && git push origin v0.x.y
  6. Monitor the claude-print-ci Argo Workflow for successful build and GitHub release

Structure

  • docs/notes/ — design decisions, constraints, integration details
  • docs/plan/plan.md — complete implementation plan
  • scripts/check-billing.sh — AS-4 billing conformance script (run before every release)
  • scripts/ — integration test scripts