claude -p returns empty stdout despite generating output tokens

Status Fixed / completed
Reported on v2.1.83
Maintainer reply None cached
Activity 4 comments · opened Mar 25, 2026 · closed Mar 31, 2026

Description

claude -p (print mode) returns empty stdout despite the model generating tokens. The --output-format json output confirms output_tokens > 0 but result is an empty string.

Environment

  • Claude Code version: 2.1.83
  • OS: Ubuntu Linux (6.8.0-106-generic)
  • Auth: OAuth (claude.ai), Max subscription
  • Installation: curl installer

Reproduction

# Simple test — returns nothing
echo "say hello" | claude -p --model haiku --max-turns 1

# JSON output confirms tokens generated but result is empty
claude -p "2+2" --output-format json
# Output: {"result":"","output_tokens":5,"stop_reason":"end_turn",...}

# Direct argument — also empty
claude -p "say hello" 2>&1
# (no output)

Observed behavior

  • Exit code: 0 (success)
  • stdout: empty
  • stderr: empty
  • --output-format json shows "result": "" with "output_tokens": 5
  • Model is charged tokens (cache_creation_input_tokens: 5205, cache_read_input_tokens: 14417)
  • Happens with and without --model haiku
  • Happens with piped stdin and direct argument
  • Happens both inside and outside an active Claude Code session
  • --bare flag results in "Not logged in" (strips OAuth auth)

Expected behavior

stdout should contain the model's response text.

Impact

This breaks any scripted use of claude -p for programmatic LLM calls in hooks/automation. We use claude -p --model haiku --max-turns 1 in Claude Code hooks for lightweight LLM tasks (query expansion, keyword extraction). All of these silently fail and fall back to non-LLM paths.

Auth verification

claude auth status
# loggedIn: true, authMethod: "claude.ai", subscriptionType: "max"

Possibly related issues

  • #28407 (Bash tool output suppressed when child process spawns claude -p)
  • #7263 (Claude CLI returns empty output with large stdin input)
  • #19663 (Bash tool returns no output — set -o onecmd)

View original on GitHub ↗

4 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/38760
  2. https://github.com/anthropics/claude-code/issues/38725
  3. https://github.com/anthropics/claude-code/issues/38769

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

brianruggieri · 5 months ago

Detailed Regression Analysis

Version Bisection

All binaries cached at ~/.local/share/claude/versions/. No v2.1.82 exists — the jump is 2.1.81 → 2.1.83 directly.

Command: <binary> -p "Reply with just: hello" --model haiku --tools "" --output-format json < /dev/null

| Version | result field | output_tokens | Status |
|---------|---------------|-----------------|--------|
| 2.1.78 | 'hello' | varies | works |
| 2.1.79 | 'hello' | varies | works |
| 2.1.80 | 'hello' | varies | works |
| 2.1.81 | 'hello' | 356 | works |
| 2.1.83 | '' | 372 | broken |

Reproducible across haiku, sonnet, and opus. With/without --tools "". From any directory.

Stream-JSON Evidence

Using --output-format stream-json --verbose, both versions produce 11 events with identical event types. The text IS generated in 2.1.83 — it just isn't captured in the result field.

Assistant text event (identical in both versions):

{"type": "assistant", "message": {"content": [{"type": "text", "text": "hello"}]}}

Result event (divergent):

// 2.1.81: {"type": "result", "result": "hello", "subtype": "success"}
// 2.1.83: {"type": "result", "result": "",      "subtype": "success"}

Structural Differences in 2.1.83

| Aspect | 2.1.81 | 2.1.83 |
|--------|--------|--------|
| Plugins loaded | 8 | 1 |
| cache_creation_input_tokens | 12,519 | 0 |
| cache_read_input_tokens | 4,851 | 17,963 |
| output_tokens | 356 | 153 |
| New native tools | — | LSP tool added |

Real-World Impact

A FastAPI server calling claude -p via subprocess.run() for NLP extraction silently fell back to a degraded keyword parser, producing incorrect results for 100% of new inputs until the empty output was diagnosed and the CLI was pinned to 2.1.81.

Known Duplicates

  • #38725, #38760, #38769

Workaround

ln -sf ~/.local/share/claude/versions/2.1.81 ~/.local/bin/claude
yurukusa · 5 months ago

Related to #38651 — Stop hooks cause empty -p output. If you have any Stop hooks configured, that is likely the root cause.
Immediate fix: Remove or disable Stop hooks:

cat ~/.claude/settings.json | jq ".hooks.Stop"

Workaround: If you need Stop hooks but also use -p mode, wrap them with a headless detector:

[ "$CC_HEADLESS" = "1" ] && exit 0
PARENT=$(ps -o args= -p $PPID 2>/dev/null || true)
echo "$PARENT" | grep -qE "\bclaude\b.*\s-p\b" && exit 0
exit 0

Or set CC_HEADLESS=1 in your wrapper:

export CC_HEADLESS=1
claude -p "$@"

This lets Stop hooks run in interactive mode but skip in -p mode, avoiding the empty output regression.

github-actions[bot] · 4 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.