Skip to main content
Glama
CLAUDE_HEADLESS.md13.5 kB
# Headless Claude Documentation This document captures the complete headless mode documentation for Claude Code CLI, used for reference when designing and implementing integration tests for the Iris MCP server. ## Overview The headless mode allows running Claude Code programmatically from command line scripts and automation tools without any interactive UI. ## Basic Usage The primary command-line interface to Claude Code is the `claude` command. Use the `--print` (or `-p`) flag to run in non-interactive mode and print the final result: ```bash claude -p "Stage my changes and write a set of commits for them" \ --allowedTools "Bash,Read" \ --permission-mode acceptEdits ``` ## Configuration Options Headless mode leverages all the CLI options available in Claude Code. Here are the key ones for automation and scripting: | Flag | Description | Example | |------|-------------|---------| | `--print`, `-p` | Run in non-interactive mode | `claude -p "query"` | | `--output-format` | Specify output format (`text`, `json`, `stream-json`) | `claude -p --output-format json` | | `--input-format` | Specify input format (`text`, `stream-json`) | `claude -p --input-format stream-json` | | `--resume`, `-r` | Resume a conversation by session ID | `claude --resume abc123` | | `--continue`, `-c` | Continue the most recent conversation | `claude --continue` | | `--verbose` | Enable verbose logging | `claude --verbose` | | `--append-system-prompt` | Append to system prompt (only with `--print`) | `claude --append-system-prompt "Custom instruction"` | | `--allowedTools` | Space-separated list of allowed tools, or string of comma-separated list of allowed tools | `claude --allowedTools mcp__slack mcp__filesystem` or `claude --allowedTools "Bash(npm install),mcp__filesystem"` | | `--disallowedTools` | Space-separated list of denied tools, or string of comma-separated list of denied tools | `claude --disallowedTools mcp__splunk mcp__github` or `claude --disallowedTools "Bash(git commit),mcp__github"` | | `--mcp-config` | Load MCP servers from a JSON file | `claude --mcp-config servers.json` | | `--permission-prompt-tool` | MCP tool for handling permission prompts (only with `--print`) | `claude --permission-prompt-tool mcp__auth__prompt` | ## statistics Context Usage Information While you can't use /context in headless mode, the final JSON result message in streaming mode includes metrics like total_cost_usd, duration_ms, and num_turns Headless mode - Claude Docs, which provides some usage information - though not the detailed breakdown that /context offers in interactive mode. ## Multi-turn Conversations For multi-turn conversations, you can resume conversations or continue from the most recent session: ```bash # Continue the most recent conversation claude --continue "Now refactor this for better performance" # Resume a specific conversation by session ID claude --resume 550e8400-e29b-41d4-a716-446655440000 "Update the tests" # Resume in non-interactive mode claude --resume 550e8400-e29b-41d4-a716-446655440000 "Fix all linting issues" --no-interactive ``` ## Output Formats ### Text Output (Default) ```bash claude -p "Explain file src/components/Header.tsx" # Output: This is a React component showing... ``` ### JSON Output Returns structured data including metadata: ```bash claude -p "How does the data layer work?" --output-format json ``` Response format: ```json { "type": "result", "subtype": "success", "total_cost_usd": 0.003, "is_error": false, "duration_ms": 1234, "duration_api_ms": 800, "num_turns": 6, "result": "The response text here...", "session_id": "abc123" } ``` ### Streaming JSON Output Streams each message as it is received: ```bash claude -p "Build an application" --output-format stream-json ``` Each conversation begins with an initial `init` system message, followed by a list of user and assistant messages, followed by a final `result` system message with stats. Each message is emitted as a separate JSON object. #### Stream-JSON Message Types 1. **System/Init Message** (first message in stream): ```json { "type": "system", "subtype": "init", "session_id": "550e8400-e29b-41d4-a716-446655440000", "cwd": "/path/to/project", "tools": ["Task", "Bash", "Read", "Write", ...], "mcp_servers": [{"name": "server1", "status": "connected"}], "model": "claude-sonnet-4-5-20250929" } ``` 2. **User Messages** (echo of sent message): ```json { "type": "user", "message": { "role": "user", "content": [ { "type": "text", "text": "Explain this code" } ] }, "session_id": "550e8400-e29b-41d4-a716-446655440000" } ``` 3. **Streaming Events** (real-time updates as Claude responds): ```json { "type": "stream_event", "event": { "type": "message_start", "message": { /* initial message structure */ } }, "session_id": "550e8400-e29b-41d4-a716-446655440000" } { "type": "stream_event", "event": { "type": "content_block_delta", "index": 0, "delta": { "type": "text_delta", "text": "partial text chunk" } }, "session_id": "550e8400-e29b-41d4-a716-446655440000" } { "type": "stream_event", "event": { "type": "message_stop" }, "session_id": "550e8400-e29b-41d4-a716-446655440000" } ``` 4. **Assistant Messages** (complete message after streaming): ```json { "type": "assistant", "message": { "role": "assistant", "content": [ { "type": "text", "text": "This code implements..." } ], "stop_reason": "end_turn", "tool_calls": [] }, "session_id": "550e8400-e29b-41d4-a716-446655440000" } ``` 5. **Result Message** (final message in stream): ```json { "type": "result", "subtype": "success", "total_cost_usd": 0.003, "is_error": false, "duration_ms": 1234, "duration_api_ms": 800, "num_turns": 6, "session_id": "550e8400-e29b-41d4-a716-446655440000" } ``` ## Input Formats ### Text Input (Default) ```bash # Direct argument claude -p "Explain this code" # From stdin echo "Explain this code" | claude -p ``` ### Streaming JSON Input A stream of messages provided via `stdin` where each message represents a user turn. This allows multiple turns of a conversation without re-launching the `claude` binary and allows providing guidance to the model while it is processing a request. Each message is a JSON 'User message' object, following the same format as the output message schema. Messages are formatted using the jsonl format where each line of input is a complete JSON object. Streaming JSON input requires `-p` and `--output-format stream-json`. ```bash echo '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Explain this code"}]}}' | claude -p --output-format=stream-json --input-format=stream-json --verbose ``` ## Implementation Notes for Iris MCP ### Key Points for Testing 1. **Process Spawning Arguments**: - Must use `--print` for headless mode - Must use `--verbose` to get stream-json output - Use `--dangerously-skip-permissions` for tests to avoid interactive prompts - Use `--input-format stream-json` and `--output-format stream-json` for bidirectional JSON communication 2. **Critical Discovery - Init Message Timing**: - **Claude does NOT send `init` message automatically on process spawn** - The `init` message is ONLY sent AFTER Claude receives the first message on stdin - You MUST send a dummy message (e.g., "ping") during spawn to trigger initialization - Only after receiving init can you consider the process ready 3. **Message Flow**: - Send: Write JSON line to process stdin - Receive: Parse JSON lines from process stdout - First message must be sent to trigger `init` response - Last message received is always `result` type - Actual responses are in `assistant` type messages (NOT `stream_event`) 4. **Stream-JSON Format Clarification**: - `--output-format stream-json` outputs NDJSON (newline-delimited JSON) - Each complete message is a separate JSON object - There are NO `stream_event` messages with `content_block_delta` - Responses come as complete `assistant` messages - Extract text from `message.content[0].text` in assistant messages 3. **Error Handling**: - Check `is_error` field in result message - Monitor stderr for debug information (when `--verbose` is used) - Process exit codes indicate failures 4. **Timing Considerations**: - Initial spawn + first response: ~13-15 seconds - Subsequent messages: 2-5 seconds - Set test timeouts accordingly (15s global is minimum) ### Example Test Communication ```javascript // Spawn process const args = [ '--print', '--verbose', '--input-format', 'stream-json', '--output-format', 'stream-json', '--dangerously-skip-permissions' ]; const process = spawn('claude', args, { cwd: teamPath }); // Send message const userMessage = { type: 'user', message: { role: 'user', content: [ { type: 'text', text: 'Hello, Claude!' } ] } }; process.stdin.write(JSON.stringify(userMessage) + '\n'); // Parse responses let initReceived = false; let responses = []; process.stdout.on('data', (data) => { const lines = data.toString().split('\n').filter(line => line.trim()); for (const line of lines) { try { const msg = JSON.parse(line); if (msg.type === 'init') { initReceived = true; sessionId = msg.session_id; } else if (msg.type === 'assistant') { responses.push(msg.message.content[0].text); } else if (msg.type === 'result') { // Conversation complete if (msg.is_error) { throw new Error('Claude returned error'); } } } catch (e) { // Not JSON, might be debug output } } }); ``` ## Best Practices 1. **Use JSON output format** for programmatic parsing of responses: ```bash result=$(claude -p "Generate code" --output-format json) code=$(echo "$result" | jq -r '.result') cost=$(echo "$result" | jq -r '.cost_usd') ``` 2. **Handle errors gracefully** - check exit codes and stderr: ```bash if ! claude -p "$prompt" 2>error.log; then echo "Error occurred:" >&2 cat error.log >&2 exit 1 fi ``` 3. **Use session management** for maintaining context in multi-turn conversations 4. **Consider timeouts** for long-running operations: ```bash timeout 300 claude -p "$complex_prompt" || echo "Timed out after 5 minutes" ``` 5. **Respect rate limits** when making multiple requests by adding delays between calls ## Testing Implications For Iris MCP integration tests: 1. **Mock Mode**: Consider creating a mock Claude process that mimics the stream-json protocol for faster tests 2. **Real Mode**: When testing with actual Claude CLI, expect 15+ second timeouts for initial responses 3. **Process Pool**: Reuse processes across tests to avoid spawn overhead 4. **Cleanup**: Always terminate processes in afterEach hooks 5. **Logging**: Capture stderr for debugging test failures ## More Notes Based on my search, here are all the message types that Claude Code returns when using --output-format stream-json: Main Message Types Each conversation begins with an initial init system message, followed by a list of user and assistant messages, followed by a final result system message with stats, with each message emitted as a separate JSON object Headless mode - Claude Docs. 1. system (with subtype init) The initial session initialization message: json{ "type": "system", "subtype": "init", "session_id": "...", "tools": [...] } 2. user User messages in the conversation: json{ "type": "user", "message": { "role": "user", "content": [...] } } 3. assistant Assistant (Claude's) responses: json{ "type": "assistant", "message": { "role": "assistant", "content": [ {"type": "text", "text": "..."}, // or {"type": "tool_use", "id": "...", "name": "...", "input": {...}} ] } } 4. tool_use (within assistant messages) When Claude invokes a tool: json{ "type": "tool_use", "name": "Bash", "input": {"command": "ls -la"} } 5. tool_result (within user messages) Results from tool execution: json{ "type": "tool_result", "tool_use_id": "...", "content": "..." } 6. result The final message containing session statistics Headless mode - Claude Docs: json{ "type": "result", "subtype": "success", "total_cost_usd": 0.003, "is_error": false, "duration_ms": 1234, "duration_api_ms": 800, "num_turns": 6, "result": "The response text here...", "session_id": "abc123" } Message Flow Example A typical conversation produces this sequence Missing Final Result Event in Streaming JSON Output with sdk · Issue #1920 · anthropics/claude-code: json{"type":"system","subtype":"init","session_id":"...","tools":[...]} {"type":"assistant","message":{"content":[{"type":"text","text":"..."}]}} {"type":"assistant","message":{"content":[{"type":"tool_use","id":"...","name":"TodoWrite",...}]}} {"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"...","content":"..."}]}} {"type":"result","subtype":"success",...} Usage This format is perfect for: Programmatic processing with jq or other JSON tools Multi-turn conversations via stdin/stdout Real-time monitoring of Claude's actions Building automation pipelines The stream-json format uses JSONL (newline-delimited JSON), where each line is a complete, parseable JSON object!

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jenova-marie/iris-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server