πŸ€– AI Tools
Β· 5 min read

Grok Build in CI/CD: Headless Mode for Automated Code Generation


Grok Build’s headless mode lets you run the agent non-interactively with grok -p "prompt". Combine it with --output-format streaming-json and you get structured output that’s easy to parse in CI/CD pipelines.

This guide covers practical automation patterns: generating tests, creating PRs, running code reviews, and integrating with GitHub Actions.

Headless mode basics

# Basic headless execution
grok -p "add unit tests for src/auth.ts"

# With structured output
grok -p "add unit tests for src/auth.ts" --output-format streaming-json

# Specify mode
grok -p "review this PR for security issues" --mode plan --output-format json

# Plain text output
grok -p "explain what src/main.ts does" --output-format text

Output formats

FormatFlagUse case
Streaming JSON--output-format streaming-jsonReal-time progress in pipelines
JSON--output-format jsonParse final result programmatically
Text--output-format textHuman-readable logs

Streaming JSON structure

Each line is a JSON object:

{"type": "status", "message": "Reading src/auth.ts..."}
{"type": "tool_call", "tool": "file_read", "path": "src/auth.ts"}
{"type": "tool_result", "tool": "file_read", "success": true}
{"type": "file_edit", "path": "src/auth.test.ts", "action": "create"}
{"type": "complete", "files_modified": ["src/auth.test.ts"], "tokens_used": 12450}

Final JSON structure

{
  "success": true,
  "files_modified": ["src/auth.test.ts"],
  "files_created": ["src/auth.test.ts"],
  "tokens_used": 12450,
  "cost": 0.015,
  "duration_ms": 8200
}

Authentication in CI

Set XAI_API_KEY as a pipeline secret. Never use browser OAuth in CI.

export XAI_API_KEY="${{ secrets.XAI_API_KEY }}"

GitHub Actions: Generate tests on PR

name: AI Test Generation
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  generate-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Grok Build
        run: curl -fsSL https://x.ai/cli/install.sh | bash

      - name: Get changed files
        id: changed
        run: |
          FILES=$(git diff --name-only origin/main...HEAD -- '*.ts' '*.tsx' | tr '\n' ' ')
          echo "files=$FILES" >> $GITHUB_OUTPUT

      - name: Generate tests
        env:
          XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
        run: |
          grok -p "Generate unit tests for these files: ${{ steps.changed.outputs.files }}. Use vitest. Only create test files, don't modify source." \
            --output-format json > result.json

      - name: Commit generated tests
        run: |
          git config user.name "grok-build[bot]"
          git config user.email "grok-build@bot"
          git add "*.test.ts" "*.test.tsx"
          git diff --cached --quiet || git commit -m "test: add AI-generated tests for changed files"
          git push

GitHub Actions: Automated code review

name: AI Code Review
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Grok Build
        run: curl -fsSL https://x.ai/cli/install.sh | bash

      - name: Get diff
        run: git diff origin/main...HEAD > pr.diff

      - name: Run AI review
        env:
          XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
        run: |
          grok -p "Review this diff for bugs, security issues, and performance problems. Be concise. Output as markdown." \
            --mode ask \
            --output-format text > review.md

      - name: Post review comment
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const review = fs.readFileSync('review.md', 'utf8');
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `## AI Code Review\n\n${review}`
            });

GitHub Actions: Auto-fix linting issues

name: AI Lint Fix
on:
  push:
    branches: [main]

jobs:
  fix:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        id: lint
        continue-on-error: true
        run: npm run lint 2> lint-errors.txt

      - name: Install Grok Build
        if: steps.lint.outcome == 'failure'
        run: curl -fsSL https://x.ai/cli/install.sh | bash

      - name: Fix lint errors
        if: steps.lint.outcome == 'failure'
        env:
          XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
        run: |
          grok -p "Fix these lint errors. Only modify the minimum code needed: $(cat lint-errors.txt)" \
            --output-format json

      - name: Create PR with fixes
        if: steps.lint.outcome == 'failure'
        run: |
          git checkout -b fix/lint-$(date +%s)
          git add -A
          git commit -m "fix: auto-fix lint errors"
          git push -u origin HEAD
          gh pr create --title "fix: auto-fix lint errors" --body "Automated lint fixes by Grok Build"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Scripting patterns

Parse streaming JSON in bash

grok -p "refactor src/utils.ts" --output-format streaming-json | while IFS= read -r line; do
  type=$(echo "$line" | jq -r '.type')
  case "$type" in
    "file_edit")
      echo "Modified: $(echo "$line" | jq -r '.path')"
      ;;
    "complete")
      echo "Done. Tokens: $(echo "$line" | jq -r '.tokens_used')"
      ;;
  esac
done

Parse final JSON in Node.js

const { execSync } = require('child_process');

const result = JSON.parse(
  execSync('grok -p "add error handling to src/api.ts" --output-format json').toString()
);

if (result.success) {
  console.log(`Modified ${result.files_modified.length} files`);
  console.log(`Cost: $${result.cost}`);
} else {
  process.exit(1);
}

Budget control in CI

# Run the task and capture output with cost info
grok -p "generate documentation" --output-format json > result.json

# Check cost after run
COST=$(cat result.json | jq '.cost')
if (( $(echo "$COST > 1.00" | bc -l) )); then
  echo "WARNING: Run exceeded $1.00 budget"
fi

Note: Check grok --help for the latest available flags as Grok Build is in early beta. Budget control flags like --max-cost may be added in future releases. For now, monitor costs using the /cost slash command in interactive sessions or parse the cost field from JSON output in CI.

Best practices for CI/CD

  1. Pin your prompts. Store prompts in files, not inline. Version control them.
  2. Monitor costs. Parse the cost field from --output-format json output and set alerts for unexpected spending. Check grok --help for budget flags as they become available.
  3. Use --mode plan for reviews. Plan mode doesn’t modify files, making it safe for read-only review jobs.
  4. Parse JSON output. Don’t rely on text parsing. Use --output-format json and parse with jq.
  5. Scope file access. Use specific file paths in prompts rather than β€œfix everything.”
  6. Test locally first. Run your headless command locally before adding it to CI.

Combining with hooks

If your project has .grok/hooks.json, hooks run in headless mode too:

{
  "post-edit": "npm run lint --fix",
  "pre-commit": "npm run test"
}

This means CI runs get the same validation as interactive sessions.

For the full command reference, see the Grok Build cheat sheet. For setup instructions, see the complete guide.


FAQ

Does headless mode support MCP servers?

Yes. MCP servers configured in ~/.grok/mcp.json or .grok/mcp.json start and connect in headless mode. This lets CI jobs access databases, GitHub APIs, or custom tools.

How do I limit costs in automated pipelines?

Monitor costs by parsing the cost field from --output-format json output after each run. Set up alerts in your CI pipeline if costs exceed a threshold. Check grok --help for the latest available flags. Budget control flags like --max-cost may be added in future releases as Grok Build is in early beta.

Can I run multiple headless instances in parallel?

Yes. Each grok -p invocation is independent. You can parallelize across files or tasks in your CI matrix. Each instance authenticates separately with the same API key.

What’s the timeout for headless mode?

Default timeout is 5 minutes. Override with --timeout 600 (in seconds) for longer tasks. CI runners may have their own timeouts that take precedence.

Does CLAUDE.md get loaded in headless mode?

Yes. If a CLAUDE.md exists in the working directory (or parent directories), Grok Build loads it in headless mode too. This ensures consistent behavior between interactive and automated runs.

Can I use headless mode with SuperGrok instead of an API key?

SuperGrok uses browser OAuth which doesn’t work in CI. For automated pipelines, you need an API key (XAI_API_KEY). You can have both: SuperGrok for interactive use, API key for CI.