Skip to content

Headless Mode

Run CodeBuddy Code programmatically without an interactive UI

Overview

Headless mode allows you to run CodeBuddy Code programmatically through command-line scripts and automation tools without any interactive UI.

Headless mode also supports scheduled task capabilities. In script, SDK, or server-side integration scenarios, you can use tools such as CronCreate, CronList, and CronDelete to create, view, and cancel scheduled tasks.

⚠️ Important Note: -y (or --dangerously-skip-permissions) is a required parameter for non-interactive mode. When using the -p/--print parameter for non-interactive execution, this parameter must be added to perform operations that require authorization (file read/write, command execution, network requests, etc.), otherwise these operations will be blocked. With -y alone, HIGH/CRITICAL dangerous commands may still require confirmation; to suppress all prompts in an isolated sandbox, use export CODEBUDDY_IS_SANDBOX=1 && codebuddy -p -y ... (high risk; process environment only). Use this only in trusted environments and for explicit task scenarios. See CLI Reference and Sandbox full pass (high risk) for details.

Basic Usage

The primary command-line interface for CodeBuddy Code is the codebuddy (or cbc) command. Use the --print (or -p) flag to run in non-interactive mode and print the final result:

bash
codebuddy -p "Stage my changes and write a set of commits for them" \
  --allowedTools "Bash,Read" \
  --permission-mode acceptEdits

Configuration Options

Headless mode leverages all available CLI options in CodeBuddy Code. Here are key options for automation and scripting:

FlagDescriptionExample
--print, -pRun in non-interactive modecodebuddy -p "query"
--output-formatSpecify output format (text, json, stream-json)codebuddy -p --output-format json
--resume, -rResume a conversation by session IDcodebuddy --resume abc123
--continue, -cContinue the most recent conversationcodebuddy --continue
--verboseEnable verbose loggingcodebuddy --verbose
--append-system-promptAppend to system prompt (only works with --print)codebuddy --append-system-prompt "Custom instructions"
--allowedToolsList of allowed tools, space-separated or

comma-separated string
codebuddy --allowedTools mcp__slack mcp__filesystem

codebuddy --allowedTools "Bash(npm install),mcp__filesystem"
--disallowedToolsList of disallowed tools, space-separated or

comma-separated string
codebuddy --disallowedTools mcp__splunk mcp__github

codebuddy --disallowedTools "Bash(git commit),mcp__github"
--settingsLoad additional settings configuration from a JSON file or JSON stringcodebuddy -p --settings '{"model":"gpt-5"}' "query"
--setting-sourcesSpecify which settings sources to load (options: user, project, local)codebuddy -p --setting-sources project,local "query"
--mcp-configLoad MCP servers from a JSON filecodebuddy --mcp-config servers.json
--permission-prompt-toolMCP tool for handling permission prompts (only works with --print)❌ Not supported

Note: The --permission-prompt-tool feature is currently not supported.

For a complete list of CLI options and features, please refer to the CLI Reference documentation.

Multi-turn Conversations

For multi-turn conversations, you can resume a conversation or continue from the most recent session:

bash
# Continue the most recent conversation
codebuddy --continue "Now refactor for better performance"

# Resume a specific conversation by session ID
codebuddy --resume 550e8400-e29b-41d4-a716-446655440000 "Update tests"

# Resume in non-interactive mode
codebuddy --resume 550e8400-e29b-41d4-a716-446655440000 "Fix all linting issues" -p

Output Formats

Text Output (Default)

bash
codebuddy -p "Explain the file src/components/Header.tsx"
# Output: This is a React component that displays...

JSON Output

Returns structured data with metadata:

bash
codebuddy -p "How does the data layer work?" --output-format json

Response format:

json
{
 ...
}

Stream JSON Output

Streams as each message is received:

bash
codebuddy -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, and ends with a final result system message containing statistics. Each message is emitted as a separate JSON object.

Background Task Events (Async)

When the model launches a background command (Bash / PowerShell), background workflow, or background Agent sub-task with run_in_background: true, the CLI emits a separate system event on the stream-json output stream for each task, carrying a unique task_id (used to distinguish concurrent tasks), with tool_use_id linking back to the tool_use that initiated the task:

  • Task started → system / subtype: "task_started"
  • Task progress (emitted per completed tool_use, sub-agent/workflow only) → system / subtype: "task_progress" (with usage)
  • Task status change → system / subtype: "task_updated" (with patch)
  • Task completed/failed/stopped → system / subtype: "task_notification"
jsonc
// Task started (pushed immediately when entering running state)
{"type":"system","subtype":"task_started","task_id":"agent-00f6","tool_use_id":"toolu_01","description":"bg agent","task_type":"Agent","uuid":"...","session_id":"..."}
// Progress (per completed tool_use, carries cumulative usage + last tool name; shell tasks do not emit)
{"type":"system","subtype":"task_progress","task_id":"agent-00f6","description":"bg agent","usage":{"total_tokens":320,"tool_uses":2,"duration_ms":157},"last_tool_name":"Bash","uuid":"...","session_id":"..."}
// Status change (patch carries changed fields; terminal state adds end_time)
{"type":"system","subtype":"task_updated","task_id":"agent-00f6","patch":{"status":"completed","end_time":1783945615966},"status":"completed","uuid":"...","session_id":"..."}
// Task completed (may arrive after the triggering turn's result; sub-agent carries usage)
{"type":"system","subtype":"task_notification","task_id":"agent-00f6","tool_use_id":"toolu_01","status":"completed","summary":"Background agent \"bg agent\" completed","output_file":"/.../bg-tasks/agent-00f6.stdout.log","usage":{"total_tokens":480,"tool_uses":2,"duration_ms":250},"session_id":"..."}

Field descriptions:

FieldEventDescription
task_idAllUnique ID for the background task, persists across started → progress → updated → notification; used to distinguish concurrent tasks and route to TaskOutput
tool_use_idMost (optional)Links back to the model's tool_use
description / task_typestarted / progressTask command description / tool type (Bash / PowerShell / Workflow / Agent)
usageprogress (always present) / notification (present for sub-agent, omitted for shell){ total_tokens, tool_uses, duration_ms } (aligns with CC's TaskUsage)
last_tool_nameprogress (optional)Name of the most recently executed tool
patch / statusupdatedChanged fields in this update (at least status; terminal state adds end_time)
statusnotificationcompleted / failed / stopped (killed/cancelled normalized to stopped)
summarynotificationHuman-readable completion summary
output_file / output_stderr_filenotification (optional)On-disk output path for the background task (file mode); use this to read the full output

Progress events (task_progress) are event-driven (one emitted per completed tool_use), not polled on a timer; background shell tasks (Bash/PowerShell) do not emit progress—only sub-agent / workflow tasks do.

Terminal state may arrive only via task_updated: Some background tasks reach their terminal state solely through task_updated (patch.status set to a terminal value) without a corresponding task_notification. Consumers tracking "active tasks" should treat the terminal status (completed / failed / stopped / killed) from both events equally when cleaning up.

Important (stdio long-lived connection scenario): Background tasks may complete after the result of the turn that triggered them. When using --input-format stream-json --output-format stream-json (a long-lived connection with stdin kept open), the CLI proactively pushes task_notification back to the same output stream after the task truly finishes—no need to send new input. Consumers should therefore keep reading the output stream and must not stop after receiving the first result, or they will miss background completion events. Pure -p single-shot mode (process exits after the first result) does not support background tasks and returns an explicit error.

Disabling background tasks: In scenarios that do not support or need background tasks, set the environment variable CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS=1 to disable them—the run_in_background parameter for Bash / PowerShell / Agent is hidden from the tool schema, and even if the model still sends it, it will be ignored/degraded to foreground execution, producing no background task events and no cross-turn push-back. The SDK's query() single-shot usage automatically injects this variable (since query() stops at the first result and cannot receive cross-turn push-back events); continuously-reading SDK usage (JS unstable_v2_createSession / Python CodeBuddySDKClient) is unaffected.

Structured JSON Output

To get output that conforms to a specific schema, use --output-format json with --json-schema and a JSON Schema definition. The response includes metadata about the request (session ID, usage, etc.), with the structured output in the structured_output field.

This example extracts function names from auth.py and returns them as an array of strings:

bash
codebuddy -p "Extract the main function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'

Tip: Use tools like jq to parse the response and extract specific fields:

bash
# Extract text result
codebuddy -p "Summarize this project" --output-format json | jq -r '.result'

# Extract structured output
codebuddy -p "Extract function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output'

Input Formats

Text Input (Default)

bash
# Direct argument
codebuddy -p "Explain this code"

# From stdin
echo "Explain this code" | codebuddy -p

Stream JSON Input

A stream of messages provided via stdin, where each message represents a user turn. This allows for multi-turn conversations without restarting the codebuddy binary, and allows providing guidance to the model while it processes requests.

Each message is a JSON "user message" object following the same format as the output message schema. Messages are formatted using jsonl format, where each line of input is a complete JSON object. Stream JSON input requires -p and --output-format stream-json.

bash
echo '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Explain this code"}]}}' | \
  codebuddy -p --output-format=stream-json --input-format=stream-json --verbose

# Single message (with image)
echo '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Text prompt, such as referring to the text in the following image"},{"type":"image","source":{"type":"base64","media_type":"image/png","data":"raw base64 (without protocol prefix)"}}]}}' \
  | codebuddy -p --input-format stream-json --output-format stream-json

# Multi-turn conversation (multi-line JSON, continuously sent to the same process)
printf '%s\n' \
  '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"First question"}]}}' \
  '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Second question"}]}}' \
  | codebuddy -p --input-format stream-json --output-format stream-json --verbose

Clients can provide a conversationRequestId for each new user turn:

json
{"type":"user","message":{"role":"user","content":"解释这段代码"},"_meta":{"codebuddy.ai/conversationRequestId":"0198a1b2c3d47e5f8a9b0c1d2e3f4a5b"}}

The value must be a lowercase, hyphenless, 32-character UUIDv7 hexadecimal string. If omitted, the CLI generates one. The CLI does not scan JSONL history for collisions; callers are responsible for ensuring uniqueness. Once accepted, the value appears in _requestId in the output for that turn.

Session Rewind

Rewind lets you restore "workspace files" and/or "conversation history" to the state before a specific historical user message was sent. This is useful when an automated Agent needs to retry after making an incorrect change, or when an IDE/upstream platform (such as CloudAgent) implements "undo to a specific message."

The naming and protocol align with Anthropic Claude Code v2.1.88, with extensions for upstream requirements.

Command-line flags (when starting a --print variant)

FlagPurposeConstraints
--rewind-files <user-message-id>Restore workspace files to the snapshot before the specified user message was sent, then exit immediatelyRequires --resume/-c; cannot be used with a prompt
--resume-session-at <message id>When resuming a session, retain only the history through the specified message (inclusive, using slice(0, index+1)), then continue the conversationRequires --resume/-c
--dry-runWhen combined with --rewind-files, preview the file changes without modifying the disk
bash
# Restore files to the state before a specific user message, then exit
codebuddy -p --resume <sessionId> --rewind-files <userMessageUuid>

# Preview only (do not modify the disk)
codebuddy -p --resume <sessionId> --rewind-files <userMessageUuid> --dry-run

# Resume the session with history truncated at a specific message, then send a new prompt
codebuddy -p --resume <sessionId> --resume-session-at <messageUuid> "Redo this step using a different approach"

These three flags are hidden (they do not appear in --help) and are intended only for scripted calls from SDKs/upstream platforms. They take effect when starting any --print variant (single-shot, --output-format json, or long-lived --input-format stream-json).

Stream JSON control protocol (long-lived runtime)

In long-lived --input-format stream-json mode, you can trigger a rewind through a control_request without restarting the process. Two subtypes are supported:

subtypeRewind scopeDescription
rewind_filesFiles onlyAligns with Claude Code; rewinds the disk without changing conversation history
rewindDetermined by scopecbc extension. scope can be Code (files only), Conversation (history only), or CodeAndConversation (default, files + history). A file rewind failure does not block history rewind (partial-success semantics)

Supported scope values for rewind:

scopeRewind scope
CodeAndConversation (default)Files + conversation history together (the actual upstream CloudAgent usage)
ConversationConversation history only; preserve files on disk
CodeFiles on disk only

Request:

jsonc
// Rewind files only (aligns with Claude Code)
{"type":"control_request","request_id":"r1","request":{"subtype":"rewind_files","user_message_id":"<uuid>","dry_run":false}}

// Rewind files + conversation history (omitting scope defaults to CodeAndConversation)
{"type":"control_request","request_id":"r2","request":{"subtype":"rewind","user_message_id":"<uuid>","dry_run":false}}

// Rewind conversation history only (preserve files on disk)
{"type":"control_request","request_id":"r3","request":{"subtype":"rewind","user_message_id":"<uuid>","scope":"Conversation"}}

Response (success):

jsonc
// rewind_files (strictly aligned with Claude's five-field schema)
{"type":"control_response","response":{"subtype":"success","request_id":"r1","response":{
  "canRewind":true,"filesChanged":["src/a.ts"],"insertions":12,"deletions":5}}}

// rewind (adds historyRewound; includes fileRewindError on partial success)
{"type":"control_response","response":{"subtype":"success","request_id":"r2","response":{
  "canRewind":true,"filesChanged":["src/a.ts"],"insertions":12,"deletions":5,"historyRewound":true}}}

Response fields:

FieldTypeMeaning
canRewindbooleanWhether rewind succeeded (for rewind, true when history rewind succeeds)
errorstring?Failure reason (precondition validation failure / history rewind failure)
filesChangedstring[]?List of affected files
insertions / deletionsnumber?Line-level insertion/deletion counts
historyRewoundboolean?(rewind only) Whether conversation history was rewound
fileRewindErrorstring?(rewind only) Partial-success indicator: canRewind:true but file rewind failed while history rewind succeeded. The upstream caller can use this to ask the user to handle files manually while the conversation context has already returned to the correct point

Key rewind semantics: a file rewind failure does not block history rewind. The handler first makes a best-effort attempt to rewind files (a failure is recorded only in fileRewindError and does not interrupt processing), then rewinds conversation history (which must succeed). Therefore, even if workspace files are in use or permissions are insufficient, the session context can still return to the correct point.

Obtaining user_message_id: In the stream-json output stream, snapshot.messageId in a file-history-snapshot message is the corresponding user message ID. You can also use the uuid of a type:"user" message directly from the output stream.

dry_run: true: Only calculates and returns a preview of filesChanged/insertions/deletions; it does not modify the disk or truncate history (historyRewound:false). This is useful for showing a confirmation dialog before performing the actual rewind.

Differences from Claude Code

CapabilityClaude Codecbc
rewind_files (file rewind)✅ Fields and semantics strictly aligned
--resume-session-at (history truncation)✅ CLI flag only✅ CLI flag
Runtime rewind (files / history / both, selected by scope)❌ Not available✅ cbc extension
Runtime conversation-history-only rewind❌ Not availablerewind + scope:"Conversation"
File rewind failure does not block history rewind✅ Partial success via fileRewindError

Calling from an SDK

The TypeScript / Python SDKs do not require new dedicated methods—rewind is a control request sent from the SDK to the CLI, so it can pass through the SDK's existing generic control_request channel. The request and response fields are the same as described above.

TypeScript SDK:

ts
// query is an SDK Query instance; transport.sendControlRequest is the existing generic channel
const resp = await query.transport.sendControlRequest<{
  canRewind: boolean;
  filesChanged?: string[];
  insertions?: number;
  deletions?: number;
  historyRewound?: boolean;
  fileRewindError?: string;
  error?: string;
}>({
  subtype: 'rewind',
  user_message_id: '<uuid>',
  scope: 'CodeAndConversation', // Default when omitted; alternatives: 'Conversation' / 'Code'
});
if (resp.canRewind && resp.fileRewindError) {
  // Partial success: history was rewound, but file rewind failed—ask the user to handle files manually
}

Python SDK:

python
# query is an SDK Query instance; _send_control_request is the existing generic channel
resp = await query._send_control_request({
    "subtype": "rewind",
    "user_message_id": "<uuid>",
    "scope": "Conversation",  # Rewind conversation history only
})
# resp: {"canRewind": True, "historyRewound": True, ...}

Type definitions (ControlRewindRequest / ControlRewindResponse / ControlRewindFilesRequest / ControlRewindFilesResponse) are exported from cbc's control-signal protocol module, so TypeScript consumers can import them directly for type hints.

Agent Integration Examples

SRE Incident Response Bot

bash
#!/bin/bash

# Automated incident response agent
investigate_incident() {
    local incident_description="$1"
    local severity="${2:-medium}"

    codebuddy -p "Incident: $incident_description (Severity: $severity)" \
      --append-system-prompt "You are an SRE expert. Diagnose the issue, assess impact, and provide immediate action items." \
      --output-format json \
      --allowedTools "Bash,Read,WebSearch,mcp__datadog" \
      --mcp-config monitoring-tools.json
}

# Usage
investigate_incident "Payment API returning 500 errors" "high"

Automated Security Review

bash
# Security audit agent for PRs
audit_pr() {
    local pr_number="$1"

    gh pr diff "$pr_number" | codebuddy -p \
      --append-system-prompt "You are a security engineer. Review this PR for vulnerabilities, unsafe patterns, and compliance issues." \
      --output-format json \
      --allowedTools "Read,Grep,WebSearch"
}

# Use and save to file
audit_pr 123 > security-report.json
bash
# Legal document review with session persistence
session_id=$(codebuddy -p "Start legal review session" --output-format json | jq -r '.session_id')

# Review contract in multiple steps
codebuddy -p --resume "$session_id" "Review liability clauses in contract.pdf"
codebuddy -p --resume "$session_id" "Check compliance with GDPR requirements"
codebuddy -p --resume "$session_id" "Generate executive summary of risks"

Best Practices

  • Use JSON output format for programmatically parsing responses:

    bash
    # Parse JSON response using jq
    result=$(codebuddy -p "Generate code" --output-format json)
    code=$(echo "$result" | jq -r '.result')
    cost=$(echo "$result" | jq -r '.total_cost_usd')
  • Handle errors gracefully - Check exit codes and stderr:

    bash
    if ! codebuddy -p "$prompt" 2>error.log; then
        echo "An error occurred:" >&2
        cat error.log >&2
        exit 1
    fi
  • Use session management to maintain context across multi-turn conversations

  • Consider timeouts for long-running operations:

    bash
    timeout 300 codebuddy -p "$complex_prompt" || echo "Timed out after 5 minutes"
  • Respect rate limits when making multiple requests, by adding delays between calls

  • Use -y to perform operations requiring authorization in non-interactive mode:

    bash
    # Complete example in non-interactive mode
    codebuddy -p "Analyze code and run tests" \
      --output-format json \
      -y \
      --allowedTools "Bash,Read,Grep"

    ⚠️ Important Note: -y (or --dangerously-skip-permissions) is a required parameter for non-interactive mode. When using the -p/--print parameter for non-interactive execution, this parameter must be added to perform operations that require authorization (file read/write, command execution, network requests, etc.), otherwise these operations will be blocked. With -y alone, HIGH/CRITICAL dangerous commands may still require confirmation; to suppress all prompts in an isolated sandbox, use export CODEBUDDY_IS_SANDBOX=1 && codebuddy -p -y ... (high risk; process environment only). Use this only in trusted environments and for explicit task scenarios. See CLI Reference and Sandbox full pass (high risk) for details.


Tip: Headless mode is ideal for CI/CD pipelines, automation scripts, and agent integrations. Combine it with MCP servers to extend functionality.