Claude CLI Agent Protocol
claude-cli-agent-protocol
Guidance for integrating Claude Code CLI in agent/headless mode: required stream-json flags, NDJSON protocol, control_request/control_response tool approvals, and process management. Use when building a host that drives Claude CLI or troubleshooting permission prompts and tool approvals.
SKILL.md
Full skill instructions
Claude CLI Agent Protocol
Overview
Enable reliable, bidirectional integration with Claude Code CLI via NDJSON, including tool approval handling, permission mode control, and long-lived process management.
Required CLI Flags
Use these flags to enable stream-json I/O and tool approval control:
claude \
--output-format stream-json \
--input-format stream-json \
--verbose \
--permission-prompt-tool stdio
Notes:
--permission-prompt-tool stdiois required for control_request/control_response. Without it, tools auto-deny in non-interactive mode.- Do not use
-pwith a prompt. Send user messages via stdin.
Message Protocol (NDJSON)
All stdin/stdout messages are newline-delimited JSON with a type field.
Send a user message:
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"your prompt here"}]}}
Core stdout message types:
system- session init (model, tools, session_id, cwd)assistant- responses and tool invocationsuser- tool results returned to Clauderesult- completion with cost/token datacontrol_request- tool approval request
Tool Approval Protocol
When a tool needs approval, CLI emits:
{
"type": "control_request",
"request_id": "req_abc123",
"request": {
"subtype": "can_use_tool",
"tool_name": "Bash",
"input": {"command": "git add -A", "description": "Stage all changes"},
"decision_reason": "Command not in allowlist",
"tool_use_id": "toolu_xyz"
}
}
Fields:
tool_name: The tool being invoked (Bash, Read, Edit, Write, etc.)input: Tool-specific input (Bash hascommandanddescription, file tools havefile_path)decision_reason: Why permission is required (e.g., "Path is outside allowed working directories")
Allow (must include updatedInput with the original or modified input):
{
"type": "control_response",
"response": {
"subtype": "success",
"request_id": "req_abc123",
"response": {
"behavior": "allow",
"updatedInput": {"command": "git add -A", "description": "Stage all changes"}
}
}
}
Deny (must include message):
{
"type": "control_response",
"response": {
"subtype": "success",
"request_id": "req_abc123",
"response": {"behavior": "deny", "message": "User denied this action"}
}
}
Key rules:
request_idmust match the incoming request.- For
allow:updatedInputis required (pass the originalinputfrom the request, or a modified version). - For
deny:messageis required (cannot be omitted). - CLI blocks waiting for a response (default timeout ~60s).
Permission Modes vs prompt tool
--permission-prompt-tool stdiois what enables control_request.--permission-mode delegateonly limits tool availability; it does not delegate approvals.
Dynamic mode switching (mid-session):
{
"type": "control_request",
"request_id": "unique-id",
"request": {
"subtype": "set_permission_mode",
"mode": "plan"
}
}
Response:
{
"type": "control_response",
"response": {
"subtype": "success",
"request_id": "unique-id"
}
}
Process Management
- Keep one CLI process alive across turns.
- Write user messages to stdin; read assistant/control messages from stdout.
- Do not start a new process per message; keep stdin open.
Cost Tracking
The final result message includes total_cost_usd, token counts, and a permission_denials list if any tool actions were denied.
Debugging
Enable debug logs via environment variable or flag:
DEBUG_CLAUDE_AGENT_SDK=1 claude ...
or --debug.
References
- Headless mode docs: https://code.claude.com/docs/en/headless
- Agent SDK permissions: https://platform.claude.com/docs/en/agent-sdk/permissions
- Agent SDK user input: https://platform.claude.com/docs/en/agent-sdk/user-input
- CLI reference: https://code.claude.com/docs/en/cli-reference
- Stream-json / control protocol gist: https://gist.github.com/SamSaffron/603648958a8c18ceae34939a8951d417
SDK Protocol Discovery (unknown behaviors/messages)
If the CLI emits unexpected message types or behaviors, inspect the TypeScript SDK:
- Download and extract the SDK package:
cd /tmp && npm pack @anthropic-ai/claude-code && tar -xzf *.tgz
- Check TypeScript definitions for message and control types:
grep -E "subtype|type.*request|Request.*=" package/sdk.d.ts
- Inspect the minified implementation for hidden flags or protocol handling:
rg -n "control_request|control_response|permission-prompt-tool|set_permission_mode" package/sdk.mjs
Key files:
package/sdk.d.ts— readable type definitions (preferred)package/sdk.mjs— minified implementation (fallback for undocumented flags)
Search targets:
SDKControlRequest/SDKControlResponsePermissionModeenumStdinMessage/StdoutMessage
