Headless Mode Guide¶
Run Nova AI without interactive UI for CI/CD, automation, and batch processing.
Time: 15 minutes
What You'll Learn¶
- Headless execution basics
- Output formats (JSON, text)
- Session management
- GitHub Actions integration
- Advanced automation patterns
- Security best practices
When to Use Headless Mode¶
| Scenario | Use Headless? | Why |
|---|---|---|
| GitHub Actions CI/CD | Yes | No terminal UI available |
| Cron jobs / scheduled tasks | Yes | Runs without interaction |
| Batch processing scripts | Yes | Programmatic control needed |
| IDE integration (Cursor) | No | Use /novaai command |
| Interactive development | No | Use /novaai command |
| One-off tasks | No | Interactive is faster |
Quick Start¶
Basic Usage¶
# Simple headless execution
python scripts/nova_headless.py "add type hints to auth.py" \
--output-format json
# With specific tool restrictions
python scripts/nova_headless.py "review code for security" \
--output-format json \
--allowed-tools "Read,Grep,Glob"
# With turn limit (cost control)
python scripts/nova_headless.py "analyze performance" \
--output-format json \
--max-turns 5
Direct Claude CLI¶
# True headless mode (no interactive UI)
claude --print "review authentication code" \
--output-format json \
--append-system-prompt "file:.claude/CLAUDE.md" \
--permission-mode acceptAll \
--no-interactive
CLI Reference¶
nova_headless.py¶
Required:
- <task> - Natural language task description
Options:
| Flag | Default | Description |
|---|---|---|
--output-format |
json |
Output format: json, text |
--max-turns |
10 |
Maximum conversation turns |
--allowed-tools |
all | Comma-separated tool list |
--timeout |
300 |
Timeout in seconds |
--session-id |
new | Resume existing session |
Claude CLI Flags¶
| Flag | Description |
|---|---|
--print |
Enable headless mode (no interactive UI) |
--output-format |
json or text output |
--permission-mode |
acceptAll for automation |
--no-interactive |
Disable all prompts |
--append-system-prompt |
Add custom instructions |
--session-id |
Resume/continue a session |
Output Formats¶
JSON Output¶
Response Structure:
{
"success": true,
"session_id": "session-abc123",
"output": "Reviewed 5 files, found 2 issues...",
"files_modified": [
"src/auth.py",
"tests/test_auth.py"
],
"metrics": {
"total_turns": 3,
"input_tokens": 15420,
"output_tokens": 2340,
"total_cost_usd": 0.0523
},
"issues": [
{
"file": "src/auth.py",
"line": 45,
"severity": "high",
"message": "SQL injection vulnerability"
}
]
}
Text Output¶
Response:
Task: add logging
Status: Success
Summary:
Added logging to 3 files with structured JSON output.
Files Modified:
- src/auth.py
- src/api.py
- src/utils.py
Cost: $0.0234
Session Management¶
Start New Session¶
# New session (default)
python scripts/nova_headless.py "implement feature X" \
--output-format json > result.json
# Extract session ID for later
SESSION_ID=$(jq -r '.session_id' result.json)
Resume Session¶
# Continue previous work (saves 88-95% overhead)
python scripts/nova_headless.py "continue with tests" \
--session-id "$SESSION_ID" \
--output-format json
Multi-Turn Workflow¶
# Step 1: Implement
python scripts/nova_headless.py "implement auth endpoint" \
--output-format json > step1.json
SESSION=$(jq -r '.session_id' step1.json)
# Step 2: Add tests (same session)
python scripts/nova_headless.py "add unit tests" \
--session-id "$SESSION" \
--output-format json > step2.json
# Step 3: Review (same session)
python scripts/nova_headless.py "review for security" \
--session-id "$SESSION" \
--output-format json > step3.json
GitHub Actions Integration¶
Basic Workflow¶
# .github/workflows/nova-ai.yml
name: Nova AI
on:
issue_comment:
types: [created]
jobs:
nova-ai:
if: contains(github.event.comment.body, '@nova-ai')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Dependencies
run: pip install anthropic
- name: Extract Command
id: extract
run: |
COMMENT="${{ github.event.comment.body }}"
COMMAND=$(echo "$COMMENT" | sed 's/@nova-ai //')
echo "command=$COMMAND" >> $GITHUB_OUTPUT
- name: Run Nova AI
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python scripts/nova_headless.py "${{ steps.extract.outputs.command }}" \
--output-format json > result.json
- name: Post Result
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const result = JSON.parse(fs.readFileSync('result.json', 'utf8'));
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Nova AI Response\n\n${result.output}\n\n**Cost:** $${result.metrics.total_cost_usd.toFixed(4)}`
});
PR Auto-Review¶
# .github/workflows/auto-review.yml
name: Auto PR Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get Changed Files
id: files
run: |
FILES=$(git diff --name-only origin/main...HEAD | tr '\n' ' ')
echo "changed=$FILES" >> $GITHUB_OUTPUT
- name: Review Changes
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
python scripts/nova_headless.py \
"review these files for security and correctness: ${{ steps.files.outputs.changed }}" \
--output-format json \
--max-turns 10 > review.json
- name: Post Review
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const review = JSON.parse(fs.readFileSync('review.json', 'utf8'));
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
event: review.issues?.length > 0 ? 'REQUEST_CHANGES' : 'APPROVE',
body: review.output
});
Nightly Security Scan¶
# .github/workflows/security-scan.yml
name: Nightly Security Scan
on:
schedule:
- cron: '0 2 * * *' # 2 AM daily
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Security Scan
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
python scripts/nova_headless.py \
"comprehensive security audit of src/" \
--output-format json \
--max-turns 20 > security-report.json
- name: Upload Report
uses: actions/upload-artifact@v4
with:
name: security-report
path: security-report.json
- name: Create Issue if Vulnerabilities Found
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('security-report.json', 'utf8'));
if (report.issues?.some(i => i.severity === 'high' || i.severity === 'critical')) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '🚨 Security vulnerabilities detected',
body: `## Security Scan Results\n\n${report.output}`,
labels: ['security', 'priority:high']
});
}
Advanced Patterns¶
Batch Processing¶
#!/usr/bin/env python3
# scripts/batch_review.py
import asyncio
import json
from pathlib import Path
from src.orchestrator.claude_sdk_executor import ClaudeSDKExecutor
async def review_files(files: list[Path]) -> list[dict]:
"""Review multiple files in parallel."""
executor = ClaudeSDKExecutor(
project_root=Path.cwd(),
agent_name="code-reviewer",
model="claude-haiku-4-5-20251001" # Cost-optimized
)
tasks = [
executor.run_task(f"Review {file} for security issues")
for file in files
]
results = await asyncio.gather(*tasks, return_exceptions=True)
return [r for r in results if not isinstance(r, Exception)]
if __name__ == "__main__":
files = list(Path("src").glob("**/*.py"))
results = asyncio.run(review_files(files))
print(json.dumps(results, indent=2))
Pipeline Integration¶
#!/bin/bash
# scripts/ci-pipeline.sh
set -e
# Step 1: Lint
echo "Running lint..."
python scripts/nova_headless.py "run ruff check and fix issues" \
--output-format json \
--max-turns 5 > lint-result.json
# Step 2: Security review
echo "Running security review..."
python scripts/nova_headless.py "security audit of changed files" \
--output-format json \
--max-turns 10 > security-result.json
# Step 3: Generate report
echo "Generating report..."
python scripts/nova_headless.py "summarize lint and security results" \
--output-format text > report.txt
# Check for failures
if jq -e '.issues | length > 0' security-result.json > /dev/null; then
echo "Security issues found!"
cat report.txt
exit 1
fi
echo "Pipeline passed!"
Webhook Handler¶
# scripts/webhook_handler.py
from flask import Flask, request, jsonify
import subprocess
import json
app = Flask(__name__)
@app.route('/nova-ai', methods=['POST'])
def handle_webhook():
"""Handle incoming webhook requests."""
data = request.json
task = data.get('task', '')
result = subprocess.run(
['python', 'scripts/nova_headless.py', task, '--output-format', 'json'],
capture_output=True,
text=True,
timeout=300
)
return jsonify(json.loads(result.stdout))
if __name__ == '__main__':
app.run(port=8080)
Security Best Practices¶
1. Never Expose API Keys¶
# CORRECT: Use secrets
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# WRONG: Hardcoded key
env:
ANTHROPIC_API_KEY: sk-ant-xxxxx # NEVER DO THIS
2. Redact Keys from Logs¶
- name: Run Nova AI
run: |
python scripts/nova_headless.py "$TASK" 2>&1 | \
sed 's/sk-ant-[a-zA-Z0-9-]*/[REDACTED]/g'
3. Restrict Tool Access¶
# Read-only operations only
python scripts/nova_headless.py "analyze code" \
--allowed-tools "Read,Grep,Glob"
# No Bash, Write, or Edit for untrusted inputs
4. Set Timeouts¶
5. Rate Limiting¶
- name: Check Budget
run: |
DAILY_COST=$(curl -s $COST_TRACKER_URL)
if [ "$DAILY_COST" -gt 100 ]; then
echo "Daily budget exceeded"
exit 1
fi
6. Input Validation¶
# Sanitize user input before passing to Nova AI
import re
def sanitize_task(task: str) -> str:
# Remove potential injection attempts
task = re.sub(r'[;&|`$]', '', task)
# Limit length
task = task[:500]
return task
Troubleshooting¶
Common Issues¶
| Issue | Solution |
|---|---|
ANTHROPIC_API_KEY not set |
Export key: export ANTHROPIC_API_KEY=sk-ant-... |
| Timeout errors | Increase --timeout or reduce task scope |
| Permission denied | Use --permission-mode acceptAll |
| Session not found | Don't use --session-id for new tasks |
| Rate limit exceeded | Reduce concurrency or wait |
Debug Mode¶
# Enable verbose logging
CLAUDE_DEBUG=1 python scripts/nova_headless.py "task" \
--output-format json
# Check Claude CLI version
claude --version
Exit Codes¶
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Failure (task failed) |
2 |
Partial success |
124 |
Timeout |
Comparison: Interactive vs Headless¶
| Feature | /novaai (Interactive) |
Headless |
|---|---|---|
| Terminal UI | Required | Not needed |
| User prompts | Yes (approval, questions) | No (auto-approve) |
| Planning phase | Full planning (~30s) | No planning |
| KB search | Automatic | Optional |
| Quality gates | All gates enforced | Optional |
| Best for | Development | CI/CD, automation |
What's Next?¶
-
CI/CD Tutorial
Full GitHub Actions setup
-
Commands
All available commands
-
Python SDK
Programmatic access