Synced from monorepo Changes: - Refresh tool search when the managed MCP catalog is re-fetched - Prevent duplicate leader process spawn and startup hang from stale leaders - Document marketplaces, plugins, and organization controls - Stamp session ID on image generation direct-to-API requests - Fix auto mode blocked documentation - Auto mode considers recent user intent - Expose deploy archive, taken-down, limit, and in-progress reasons on the chat API - Fail-closed auth refresh contract for shell clients - Emit a chat-supplied per-session turn index in turn hooks - Show bash mode chrome in minimal mode - Add metrics for true-noop and stationarity stops - Include voice interim text on prompt submit - Silently end turn on true-noop thrash - Quiet copy toast when clipboard delivery is confirmed - Fix session fork truncating at the wrong prompt in rewound sessions - Make the idle "still running" watcher cue clickable to open the tasks pane - Default web search model to grok-4.5 - Let plugin subagents inherit parent MCP servers - Gate no-op end-turn reminder on system reminders - Add gateway bridge lifecycle telemetry - Allow editing finalized text while voice is open - Relocate token carrier to turn-commit events and plumb per-turn origin context - Raise workflow scratch quotas and make failed runs resumable - Workflows overlay: auto-progress phases, live agent status, and drop budget meter Source-Revision: 9b8d35b46d959c042ea9aa31cbbebbd1f0c5c527
25 KiB
Headless Mode and Scripting
Headless mode runs Grok non-interactively from the command line. It accepts a single prompt, executes it with full tool access, and returns the result. Use it to automate tasks, script workflows, build integrations, and parse output programmatically.
Basic Usage
Passing a prompt non-interactively triggers headless mode. The most common way is the -p flag (short for --single); --prompt-json and --prompt-file also trigger it:
grok -p "Your prompt here"
Grok processes the prompt, runs any necessary tools, and prints the result to stdout. The process exits when the response is complete.
Command-Line Options
| Flag | Description |
|---|---|
-p, --single <PROMPT> |
The prompt to send (or use --prompt-json / --prompt-file) |
-m, --model <MODEL> |
Model to use (e.g., grok-build) |
-s, --session-id <ID> |
Create a new session with this UUID (errors if invalid UUID or already in use under the target session directory; does not resume — use -r/-c) |
--fork-session |
With -r/-c, fork into a new session ID instead of appending to the original |
-r, --resume <ID_OR_TITLE> |
Resume an existing session by ID, or by title for the current directory, ignoring letter case (a sole manually renamed match wins among duplicates; remaining duplicates error with their IDs; UUID-shaped values always take the ID path; scripts should prefer IDs) |
-c, --continue |
Continue the most recent session in current directory |
--cwd <PATH> |
Set working directory |
--output-format <FMT> |
Output format: plain, json, streaming-json |
--yolo |
Auto-approve all tool executions |
--rules <TEXT> |
Custom rules for the system prompt |
--tools <TOOLS> |
Allowlist of built-in tools (comma-separated). MCP meta-tools remain available unless denied. Headless only. |
--disallowed-tools <TOOLS> |
Denylist of built-in tools to remove (comma-separated). Supports Agent entries. Headless only. |
--max-turns <N> |
Maximum number of agentic turns before stopping. Headless only. |
--reasoning-effort / --effort <LEVEL> |
Reasoning effort for reasoning models. Canonical levels: none, minimal, low, medium, high, xhigh, max (each a distinct tier; a model only accepts the levels its menu advertises). Also accepts per-model menu option ids (e.g. deep → mapped wire value), same as /effort. Works in TUI and headless. |
--permission-mode <MODE> |
Permission mode. bypassPermissions enables always-approve (see Permissions and safety); for deny-by-default use defaultMode in .claude/settings.json. |
--allow <RULE> |
Permission allow rule with glob patterns (repeatable). Works in TUI and headless. |
--deny <RULE> |
Permission deny rule with glob patterns (repeatable). Works in TUI and headless. |
--prompt-json <JSON> |
Prompt as JSON content blocks |
--prompt-file <PATH> |
Prompt from a file |
--verbatim |
Send prompt exactly as given |
--no-auto-update |
Disable update checks for this session |
--sandbox <PROFILE> |
Sandbox profile for filesystem/network access |
Note:
--tools,--disallowed-tools,--max-turns, and--agentsare headless-only flags. If used in the interactive TUI, a warning is printed and the flag is ignored.--reasoning-effort/--effort,--permission-mode,--allow, and--denywork in both modes. For more flags (agents and worktrees), see Additional Headless Flags.
Tool Filtering
Use --tools to restrict the agent to an explicit set of tools (allowlist), or --disallowed-tools to remove specific tools from the default set (denylist). Both accept comma-separated tool names.
Tool names are internal tool IDs (e.g. the shell tool is run_terminal_cmd, not bash).
# Only allow read-only tools
grok -p "Explain this codebase" --tools "read_file,grep,list_dir"
# Remove web access and file editing
grok -p "Review this code" --disallowed-tools "web_search,web_fetch,search_replace"
# Remove shell access
grok -p "Review this code" --disallowed-tools "run_terminal_cmd"
--disallowed-tools also supports special Agent entries to control subagent spawning:
| Entry | Effect |
|---|---|
Agent |
Block all subagent spawning |
Agent(explore) |
Block the explore subagent type only |
Agent(explore, plan) |
Block multiple specific types |
# Prevent the agent from spawning any subagents
grok -p "Fix this bug" --disallowed-tools "Agent"
# Block only the explore subagent
grok -p "Refactor this module" --disallowed-tools "Agent(explore)"
--tools preserves the selected agent profile's injection policy: stock profiles inject enabled optional tools before applying the allowlist, while curated profiles remain strict. The final toolset retains requested tools plus always-on MCP meta-tools. When both flags are present, --disallowed-tools wins.
Permission Rules (--allow / --deny)
Permission rules control whether specific tool invocations are auto-approved, denied, or require user confirmation. Unlike --disallowed-tools (which removes tools entirely), permission rules leave tools available but gate their execution.
Rules use ToolPrefix(glob_pattern) syntax:
| Prefix | What it controls |
|---|---|
Bash(...) |
Shell command execution |
Edit(...) |
File editing (path glob) |
Write(...) |
File writing (path glob) |
Read(...) |
File reading (path glob) |
Grep(...) |
Search operations (path glob) |
WebFetch(...) |
URL fetching (glob or domain:host) |
MCPTool(...) |
MCP tool invocations |
For path rules (Read, Edit, Write, Grep), * is a single-level wildcard and ** is recursive. For Bash rules, * matches any characters including spaces. A bare prefix without parentheses matches all invocations of that type, and Bash(cmd:*) is equivalent to prefix matching on cmd. See 22-permissions-and-safety.md for the full matching semantics.
# Deny shell commands matching "rm*"
grok -p "Clean up this project" --deny "Bash(rm*)"
# Allow npm commands, deny sudo
grok -p "Set up the project" --allow "Bash(npm*)" --deny "Bash(sudo*)"
# Allow all bash commands (auto-approve without prompting)
grok -p "Build the project" --allow "Bash"
--allow and --deny can be repeated. Deny rules take precedence over allow rules.
Output Formats
Headless mode supports three output formats, selected with --output-format.
plain (default)
Human-readable text, suitable for direct display or piping:
Here's a summary of the codebase...
json
A single JSON object emitted after the response completes: response text,
stop reason, session ID, request ID (plus thought when reasoning is present).
When the prompt reached the model, the same object also carries spend fields
(usage, num_turns, modelUsage, cost).
{
"text": "Here's a summary of the codebase...",
"stopReason": "EndTurn",
"sessionId": "abc123",
"requestId": "xyz789",
"num_turns": 7,
"usage": {
"input_tokens": 7210,
"cache_read_input_tokens": 41000,
"output_tokens": 1893,
"reasoning_tokens": 412,
"total_tokens": 50103
},
"modelUsage": {
"grok-build": {
"inputTokens": 7210,
"outputTokens": 1893,
"cacheReadInputTokens": 41000,
"modelCalls": 7,
"costUSD": 0.01268905
}
},
"total_cost_usd": 0.01268905,
"total_cost_usd_ticks": 126890500
}
Usage notes:
usagesums tokens for the prompt, including subagents that finished before turn end (also under their ownmodelUsagekeys). Compaction and other side-model calls are excluded.- Token field policy (headless result /
end/ error spend):usage.input_tokensandmodelUsage.*.inputTokensare uncached only.cache_read_input_tokens/cacheReadInputTokensare cache hits.total_tokensis full input + output (includes cache):total_tokens = input_tokens + cache_read_input_tokens + output_tokens.- ACP
_meta.usage.inputTokens(PromptUsage) is still the full prompt sum; only the headless projector subtracts cache. Prefer headless fields for spend automation.
num_turnscounts main-agent model rounds recorded on the prompt ledger (tool-loop rounds that reported usage). Subagent sampler calls do not increase it. Per-model call counts (including subagents) stay onmodelUsage.*.modelCalls. This is the same counter family as--max-turns, not a guarantee of exact equality when rounds lack usage or hit gates.total_cost_usdappears only when the server reported a complete cost. Absence means unreported or incomplete, never free. Cost is stamped for API-key traffic today; pool/OAuth paths often omit it until the server stamps cost. When some calls lacked cost,cost_is_partialis true and all cost floats are omitted (total_cost_usdand everymodelUsage.*.costUSD) so consumers cannot sum model rows into a fake complete bill.total_cost_usd_ticksis the same value in exact integer ticks (1 USD = 10^10 ticks) and appears under the same conditions. Use it for billing reconciliation: summing per-invocation ticks matches the server's usage export exactly, which float dollars cannot guarantee.- When subagent usage could not be applied, nested subagent usage was incomplete,
or the success-path drain timed out (up to 120s on the turn task),
usage_is_incompleteis true and cost floats are omitted the same way (token totals may under-count subagents). Cancel snapshots without that long drain and marks incomplete while subagents are still live. Incomplete with no recorded tokens emits onlyusage_is_incomplete(no zerousageobject). - A prompt that never reached the model omits the spend fields.
The sessionId field is useful for resuming the conversation later.
On failure, Grok emits an error object (process exit non-zero). Prompt-level failures may also include frozen spend fields when usage was recorded:
{"type":"error","message":"Couldn't start session: ..."}
streaming-json
Newline-delimited JSON events emitted in real time. Each line is a self-contained JSON object with a type field:
{"type":"text","data":"Here's"}
{"type":"text","data":" a summary"}
{"type":"thought","data":"Analyzing the directory structure..."}
{"type":"end","stopReason":"EndTurn","sessionId":"abc123","requestId":"xyz789","usage":{...},"num_turns":7,"modelUsage":{...}}
Event types:
| Type | Description |
|---|---|
text |
A chunk of the agent's response text |
thought |
Internal reasoning (thinking tokens) |
end |
Final event with metadata and spend fields when available |
error |
An error occurred (carries message, and spend fields if any) |
end is always the last event. Spend fields on end match the json object
shape (snake_case uncached input_tokens, safe cost floats).
Grok may also emit max_turns_reached and auto_compact_* events; treat the list as non-exhaustive and switch on type.
Session Management in Headless Mode
By default, each grok -p invocation creates a fresh session. To maintain context across calls, use session flags.
Named Sessions (-s)
To carry context across headless calls, use -r/--resume or -c/--continue. Use -s/--session-id only for a new session with a UUID (errors if not a UUID or already in use under the target directory). Older hidden -s upsert/resume behavior is gone — use -r/-c to continue. With -r/-c, -s requires --fork-session:
# Start a headless session and capture its ID
grok -p "Review the changes in this PR" --output-format json | jq -r '.sessionId'
# Continue in the same session
grok -p "Now check for security issues" --resume "<id>"
# Optional: create with a client-chosen UUID (must not already exist)
grok -p "hello" --session-id "$(uuidgen | tr '[:upper:]' '[:lower:]')" --output-format json
Note:
-s/--session-idcreates a new session only (valid UUID; errors if already in use). Use-rto resume.
Resume (-r)
The -r/--resume flag resumes a specific session by ID, or by title for the current directory when the value is not an ID, ignoring letter case (a sole manually renamed match wins among duplicates; remaining duplicates error with their IDs; UUID-shaped values always take the ID path — scripts should prefer IDs). It errors if the session does not exist:
# Get the session ID from a previous JSON response
grok -p "Remember: the secret number is 42" --output-format json
# Output includes "sessionId": "abc123"
# Resume that exact session
grok -p "What's the secret number?" --resume abc123
Continue (-c)
The -c/--continue flag continues the most recent session in the current working directory:
grok -p "Continue where we left off" -c
Extracting Session IDs
Use --output-format json and parse the sessionId field:
grok -p "Hello" --output-format json | jq -r '.sessionId'
Piping Input and Output
Headless mode works naturally with Unix pipes and redirection.
Standard Output
# Pipe output to a file
grok -p "Generate a README" > README.md
# Parse JSON output with jq
grok -p "List files" --output-format json | jq -r '.text'
Standard Input
Headless mode does not read piped stdin into the prompt. Pass external content through command substitution or --prompt-file:
# Include git diff as context via command substitution
grok -p "Write a concise commit message for these changes:
$(git diff --staged)"
# Or read the prompt from a file
grok --prompt-file ./prompt.txt
CI/CD Integration Examples
Automated Code Review
grok -p "Review changes for bugs and security issues." \
--output-format json --yolo | jq -r '.text' > review.md
Pre-Commit Hook
grok -p "Review staged changes for obvious bugs. Reply OK if fine, or list issues." \
--yolo --output-format json | jq -r '.text' | grep -q "^OK" || exit 1
Batch Processing
for file in src/*.js; do
grok -p "Migrate $file from CommonJS to ES modules." --yolo
done
Scripting Patterns
Python Wrapper
Grok's headless mode can be wrapped as an OpenAI-compatible chat completion API:
import asyncio
import json
import os
class GrokChat:
"""Simple OpenAI-compatible wrapper using headless mode."""
def __init__(self, cwd="."):
self.cwd = cwd
self.env = {**os.environ}
def _build_cmd(self, prompt, model, stream):
return ["grok", "-p", prompt, "-m", model, "--cwd", self.cwd,
"--output-format", "streaming-json" if stream else "json",
"--yolo"]
async def create(self, messages, model="grok-build", stream=False):
prompt = messages[-1]["content"] if len(messages) == 1 else "\n".join(
f"{m['role']}: {m['content']}" for m in messages
)
cmd = self._build_cmd(prompt, model, stream)
if stream:
return self._stream(cmd)
proc = await asyncio.create_subprocess_exec(
*cmd, env=self.env, stdout=asyncio.subprocess.PIPE
)
stdout, _ = await proc.communicate()
data = json.loads(stdout.decode()) if stdout else {"text": ""}
return {
"choices": [{
"message": {"role": "assistant", "content": data.get("text", "")},
"finish_reason": "stop"
}]
}
async def _stream(self, cmd):
proc = await asyncio.create_subprocess_exec(
*cmd, env=self.env, stdout=asyncio.subprocess.PIPE
)
async for line in proc.stdout:
if not line.strip():
continue
event = json.loads(line)
if event.get("type") == "text":
yield {"choices": [{"delta": {"content": event["data"]}}]}
elif event.get("type") == "end":
yield {"choices": [{"delta": {}, "finish_reason": "stop"}]}
async def main():
client = GrokChat(cwd=".")
response = await client.create(
[{"role": "user", "content": "What files are here?"}]
)
print(response["choices"][0]["message"]["content"])
asyncio.run(main())
Shell Script
#!/bin/bash
# Run a code review and exit with failure if issues are found
RESULT=$(grok -p "Review this PR for bugs. Output JSON with 'issues' array." \
--output-format json --yolo | jq -r '.text')
ISSUE_COUNT=$(echo "$RESULT" | jq '.issues | length' 2>/dev/null || echo "0")
if [ "$ISSUE_COUNT" -gt 0 ]; then
echo "Found $ISSUE_COUNT issues"
echo "$RESULT" | jq '.issues[]'
exit 1
fi
echo "No issues found"
Always-approve for automation
--always-approve (alias --yolo, same as --permission-mode bypassPermissions) runs tool calls without interactive permission prompts. Deny rules, hooks, and admin locks still apply (see Permissions and safety).
grok -p "Format all files" --always-approve
grok -p "Run the tests and fix any failures" --cwd ~/projects/my-app --always-approve
For agent servers and SDKs, see Agent mode.
Environment Variables for Headless
Key environment variables that affect headless mode:
| Variable | Description |
|---|---|
XAI_API_KEY |
API key for authentication (required when no browser login) |
GROK_HOME |
Override config directory (default: ~/.grok) |
GROK_LOG_FILE |
Path to a log file (used verbatim as the path; works in headless and TUI, honors RUST_LOG) |
RUST_LOG |
Log level filter (e.g. debug). Headless logs to stderr. |
For CI environments without browser access, set XAI_API_KEY with an API key from console.x.ai:
export XAI_API_KEY="xai-..."
grok -p "Run the test suite" --yolo
Exit Codes
| Code | Meaning |
|---|---|
0 |
Success -- prompt completed normally |
1 |
Error -- authentication failure, network error, or runtime error |
130 |
Interrupted by SIGINT (Ctrl+C) |
143 |
Terminated by SIGTERM |
Authentication for Headless Environments
For headless use, authenticate with one of:
XAI_API_KEY— simplest for CI. See Environment Variables above.grok login --device-auth(or--device-code) — no browser needed on the target machine. See Authentication > Device Code Flow.grok login— browser-based OAuth2 on machines with a GUI.
If you've previously logged in, cached credentials are used automatically.
Tips
- Headless mode starts a fresh session by default. Use
-r/--resumeor-c/--continueto maintain context across calls. - The
--output-format jsonresponse always includes asessionIdyou can use with--resumefor follow-up calls. - Combine
--yolowith--rulesto set guardrails:grok -p "..." --yolo --rules "Never delete files". - For debugging, raise the log level and capture stderr:
RUST_LOG=debug grok -p "..." 2> debug.log.
Project Root Discovery
When Grok starts, it discovers the project root by walking upward from --cwd
(or the current directory) until it finds a .git directory.
Note: If --cwd is nested inside a large repository (such as a monorepo),
Grok discovers that repository as the project root and scopes its discovery (AGENTS.md, skills, git history) to it, which can make
startup slow. Point --cwd at the specific subproject you want to work in to keep
the scope small.
File Locations
Grok stores data in ~/.grok (override with GROK_HOME; see Environment Variables for Headless):
| Path | Contents |
|---|---|
config.toml |
User configuration |
auth.json |
Cached OAuth2/API credentials |
version.json |
Version cache for update checks |
sessions/ |
Session transcripts (SQLite) |
memory/ |
Cross-session memory store |
logs/ |
Internal log files (for example unified.jsonl) |
logs/mcp/ |
MCP server logs |
skills/ |
User skill definitions |
personas/ |
User-scoped agent personas |
crash/ |
Crash reports |
trace-exports/ |
Session trace exports |
worktrees/ |
Git worktree metadata |
Read-Only ~/.grok
For containers or CI, mount ~/.grok read-only:
- Pre-populate
auth.jsonor useXAI_API_KEY - Session persistence fails silently (ephemeral)
- Update checks log a warning and skip
export XAI_API_KEY="xai-..."
export GROK_DISABLE_AUTOUPDATER=1
grok -p "..." --no-auto-update
Update Check Suppression
| Method | Scope |
|---|---|
--no-auto-update |
Session |
GROK_DISABLE_AUTOUPDATER=1 |
Process |
| Non-TTY stderr (auto-detected) | Automatic |
[cli] auto_update = false |
Persistent |
GROK_DISABLE_AUTOUPDATER set to a falsy value (0, false, off, no, or empty, any
case) counts as not set. The agent SDKs
inject GROK_DISABLE_AUTOUPDATER=1 for the non-leader agents they spawn (a falsy value in
the SDK's isolation env keeps updates on), and the stdio agent skips its background update
unless it runs from the managed install ($GROK_HOME/bin/grok).
Update messages go to stderr. Stdout stays clean for --output-format json. See also Environment Variables for Headless.
Additional Headless Flags
These flags supplement the Command-Line Options table above. Flags already listed there (--prompt-json, --prompt-file, --verbatim, --sandbox, --no-auto-update) are not repeated here.
| Flag | Description |
|---|---|
--agent <NAME> |
Agent name or definition file path |
--agents <JSON> |
Inline subagent definitions as JSON |
--system-prompt-override |
Override the agent's system prompt |
--no-plan |
Disable plan mode |
--no-subagents |
Disable subagent spawning |
--no-memory |
Disable cross-session memory |
--disable-web-search |
Disable web search and fetch tools |
--no-alt-screen |
Run inline (no alternate screen) |
--worktree [NAME] |
Start session in a new git worktree |
--ref <REF> / --worktree-ref <REF> |
Branch/tag/commit to base the worktree on (with --worktree) |
Interrupted Headless Runs
On SIGINT/SIGTERM:
- Session state saved up to the last completed tool call
- File modifications by tools are not rolled back
- Exit code is 130 for SIGINT (
128 + 2) and 143 for SIGTERM (128 + 15); CI pipelines can distinguish these from a normal error (exit code1) - Resume:
grok -p "continue" --resume "<id>"orgrok -p "continue" --continue
See Session Management in Headless Mode for details on named sessions and the -s/-r/-c flags.