Skip to content

pdalinis/claude-batchy

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-batchy

License: MIT Claude Code Skill Anthropic Batch API Python

A Claude Code skill that sends non-urgent work to the Anthropic Message Batches API at 50% cost.

Code reviews, documentation, architecture analysis, refactoring plans, security audits — anything that can wait ~1 hour gets half-price processing with Claude Opus (or any batch-capable Anthropic model).

This is the skill-only variant: no MCP server, no long-running process. The /batchy skill drives a single Python CLI (batch.py) via uv run. All state is on disk under ~/.claude/batches/.

Fork of s2-streamstore/claude-batch-toolkit — strips out the MCP server and Vertex AI backend, adds safety hardening. See What this fork changes and Credits.

What this fork changes

Upstream supports two backends (Anthropic Message Batches + Vertex AI BatchPredictionJobs) and ships an MCP server wrapping the CLI. This fork strips both layers down to a single-backend, skill-only toolkit. Concretely:

  • No MCP server. Every MCP tool already had a CLI equivalent and all state lived on disk, so the MCP layer was pure ceremony. The /batchy skill now invokes batch.py directly via shell. No mcpServers.claude-batch entry in ~/.claude.json, no mcp PyPI dependency, no "MCP server not responding" failure mode, every batch action visible as a Bash tool call.
  • No Vertex AI backend. Removed the VertexBatchBackend class, the google-auth / google-cloud-storage PyPI deps, the VERTEX_* env vars, the --backend CLI flag, and the GCS upload/download paths. If you need Vertex, use upstream.
  • Tightened packet_path allowlist. The CLI rejects packet files outside cwd / tempdir / ~/.claude/batches/ (override via CLAUDE_BATCH_PACKET_ROOTS) to defeat prompt-injection-driven file exfiltration.
  • Safer statusline. Replaced source ~/.claude/env with a grep | sed extraction so a tampered env file can no longer execute arbitrary shell on every assistant turn.
  • Cross-process jobs.json lock. A .jobs.lock.d directory mutex shared by batch.py and statusline.sh prevents either process from clobbering the other's updates.
  • poll_once logs to stderr instead of silently swallowing exceptions (set CLAUDE_BATCH_DEBUG=1 for tracebacks).
  • Default model bumped claude-opus-4-6claude-opus-4-8; default max_tokens 3276865536. Opt-in auto-tracking via CLAUDE_MODEL=auto-opus (newest Opus) or auto-latest (newest in any family, may pick Fable 5) — see Auto-tracking the latest model.
  • PyPI deps pinned to compatible ranges. Offline test suite under tests/.

Net change vs upstream: roughly -450 lines of code/docs, plus +250 lines of tests.

Vertex jobs from a prior install will still appear in list (the loader filters legacy fields gracefully) but cannot be polled — re-submit them through Anthropic, or use upstream.

Install

git clone git@github.com:pdalinis/claude-batchy.git
cd claude-batchy
./install.sh --api-key sk-ant-your-key-here

The installer shows a manifest of every change it will make and asks for confirmation before proceeding.

Install Options

Flag Description
--api-key KEY Your Anthropic API key (required unless already in env)
--no-poller Skip status line configuration
--unattended No interactive prompts

Uninstall

./uninstall.sh

This shows what will be removed, asks for confirmation, and preserves your results in ~/.claude/batches/results/. Use --purge-data to also remove results.

Manual Installation (no script)

If you prefer not to run the install script — or need to install in a restricted environment — follow these steps to set up each component by hand.

Prerequisites

Dependency Purpose Install
uv Runs the Python CLI (no virtualenv needed) curl -LsSf https://astral.sh/uv/install.sh | sh
jq JSON processing in statusline + installer brew install jq or apt-get install jq
curl Polls the Anthropic API from statusline brew install curl or apt-get install curl

You also need an Anthropic API key (sk-ant-...). Get one from console.anthropic.com.

Step 1: Create directory structure

mkdir -p ~/.claude/skills/batchy
mkdir -p ~/.claude/batches/results

Step 2: Install the CLI and skill

cp skills/batchy/batch.py  ~/.claude/skills/batchy/batch.py
cp skills/batchy/SKILL.md  ~/.claude/skills/batchy/SKILL.md
chmod +x ~/.claude/skills/batchy/batch.py

Step 3 (optional): Install the statusline script

Skip this step if you don't want batch job counts in your Claude Code status bar. Everything else works without it.

cp statusline.sh ~/.claude/statusline.sh
chmod +x ~/.claude/statusline.sh

Step 4: Set up your API key

The toolkit reads ANTHROPIC_API_KEY from ~/.claude/env. This file must be mode 600.

echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR-KEY-HERE"' > ~/.claude/env
chmod 600 ~/.claude/env

If ~/.claude/env already exists, edit it to add/replace the ANTHROPIC_API_KEY line, then chmod 600 ~/.claude/env.

Step 5 (optional): Configure the statusline in ~/.claude/settings.json

Skip if you skipped Step 3.

jq -n --arg cmd "bash $HOME/.claude/statusline.sh" '{
  "statusLine": {"type": "command", "command": $cmd}
}' > ~/.claude/settings.json

If ~/.claude/settings.json already exists, merge instead of overwriting:

jq --arg cmd "bash $HOME/.claude/statusline.sh" '
  .statusLine = {"type": "command", "command": $cmd}
' ~/.claude/settings.json > ~/.claude/settings.json.tmp \
  && mv ~/.claude/settings.json.tmp ~/.claude/settings.json

Step 6: Initialize the jobs registry

if [ ! -f ~/.claude/batches/jobs.json ]; then
  echo '{"version": 1, "jobs": {}}' | jq '.' > ~/.claude/batches/jobs.json
fi

Step 7: Smoke test

source ~/.claude/env
uv run ~/.claude/skills/batchy/batch.py list

Expected: empty JSON array or a list of existing jobs. First run may take a moment while uv resolves dependencies.

Manual Uninstall

# Toolkit files
rm -f ~/.claude/skills/batchy/batch.py
rm -f ~/.claude/skills/batchy/SKILL.md
rm -f ~/.claude/statusline.sh
rmdir ~/.claude/skills/batchy 2>/dev/null || true
rmdir ~/.claude/skills 2>/dev/null || true

# Statusline (if installed)
jq 'del(.statusLine)' ~/.claude/settings.json > ~/.claude/settings.json.tmp \
  && mv ~/.claude/settings.json.tmp ~/.claude/settings.json

# API key (optional)
grep -v '^export ANTHROPIC_API_KEY=' ~/.claude/env > ~/.claude/env.tmp \
  && mv ~/.claude/env.tmp ~/.claude/env && chmod 600 ~/.claude/env
[ ! -s ~/.claude/env ] && rm -f ~/.claude/env

# Jobs data (optional)
# rm -rf ~/.claude/batches

Why /batchy?

The skill was originally named /batch, but Claude Code introduced a built-in /batch command with different semantics (#4). To avoid the collision, we renamed to /batchy. Existing installs are migrated automatically when you re-run ./install.sh.

If you previously installed the MCP-based upstream version, ./install.sh cleans up ~/.claude/mcp/ and removes the mcpServers.claude-batch entry automatically. See What this fork changes for the full rationale.

Usage

Submit work to batch

In Claude Code, just say:

/batchy Review this codebase for security issues
/batchy Generate comprehensive tests for src/auth/
/batchy Write API documentation for all public endpoints

Claude will gather all relevant context, build a self-contained prompt file with mktemp, run batch.py submit, and tell you the job ID.

Check results

/batchy check
/batchy status
/batchy list

Results appear in your status bar automatically. When a job completes, Claude reads the result file from disk and presents it.

Direct CLI usage

The same CLI also works standalone:

# Submit a job
uv run ~/.claude/skills/batchy/batch.py submit --packet-path prompt.md --label "security-review"

# List all jobs
uv run ~/.claude/skills/batchy/batch.py list

# Poll for completed jobs
uv run ~/.claude/skills/batchy/batch.py poll

# Fetch a specific result
uv run ~/.claude/skills/batchy/batch.py fetch msgbatch_xxx --print

How It Works

┌─────────────────────────────────────────────────────────────────┐
│ Claude Code Session                                             │
│                                                                 │
│  User: "/batchy review src/ for security issues"                │
│                                                                 │
│  Claude:                                                        │
│    1. mktemp → $TMPDIR/batchy-XXXXXX.md                         │
│    2. Assembles self-contained prompt (bash cat → temp file)    │
│    3. Runs: uv run ~/.claude/skills/batchy/batch.py submit ...  │
│    4. Parses {"job_id": "msgbatch_..."} from stdout             │
│    5. Reports: "Submitted job msgbatch_abc123"                  │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ Status Bar                                              │    │
│  │ [Opus] 42% | $1.23 | batch: 1 pending                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  ... ~30 minutes later ...                                      │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ Status Bar                                              │    │
│  │ [Opus] 42% | $1.23 | batch: 1 done                      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  User: "/batchy check"                                          │
│  Claude: cat ~/.claude/batches/results/msgbatch_abc123.md       │
│          presents formatted results                             │
└─────────────────────────────────────────────────────────────────┘

                          │
                          ▼
                ┌──────────────────┐
                │ Anthropic Batch  │
                │ API (direct)     │
                │ 50% cost         │
                │ ~1hr turnaround  │
                └──────────────────┘

Status Line + Cached Poller

The status line is the only moving part — no daemons, no background services, no launchd/systemd.

Assistant message arrives
        │
        ▼
 statusline.sh runs
        │
        ├─► Render (instant): Read jobs.json → print status bar
        │
        └─► Poll (async fork): If pending jobs + cache stale (>60s)
            └─► curl Anthropic API → update jobs.json
                (never blocks the status line)
Property Value
Blocks status line? Never — poll is forked
Polls when idle? No — only during active Claude sessions
Poll frequency At most once per 60s
Extra processes None — no daemon
Wasted API calls Zero when no pending jobs

Configuration

Environment Variables

Variable Default Description
ANTHROPIC_API_KEY Your Anthropic API key (required)
CLAUDE_BATCH_DIR ~/.claude/batches Where jobs.json and results live
CLAUDE_MODEL claude-opus-4-8 Model id, or one of the sentinels auto-opus / auto-latest (see Auto-tracking the latest model)
CLAUDE_MAX_TOKENS 65536 Max output tokens (Opus 4.7+ supports up to 128k sync / 300k batch-beta)
CLAUDE_THINKING Set to enabled for extended thinking
CLAUDE_THINKING_BUDGET Token budget for thinking

Auto-tracking the latest model

The default CLAUDE_MODEL is pinned (currently claude-opus-4-8) for reproducibility — a batch you submit today and another tomorrow use the same model. When Anthropic ships a new model, the default goes stale.

Two sentinel values track Anthropic's catalog automatically. Both call the Models API (GET /v1/models) on submit, filter for capabilities.batch.supported, take the newest match (the API returns entries newest-first), and cache the result for 24 hours under ~/.claude/batches/.model_cache.json. Cache miss adds ~300ms; the cache is keyed by sentinel so switching between them doesn't pick up stale values.

Sentinel What it picks When to use
CLAUDE_MODEL=auto-opus Newest batch-capable model whose id starts with claude-opus- You want auto-tracking with predictable cost behavior — Opus pricing has been stable across versions
CLAUDE_MODEL=auto-latest Newest batch-capable model in any family (may select Fable 5) You want the most capable available model regardless of cost or family

Or do a one-shot lookup and pin the result yourself:

# Print the newest batch-capable Opus
uv run ~/.claude/skills/batchy/batch.py latest-opus
# → claude-opus-4-8

# Print the newest batch-capable model in any family
uv run ~/.claude/skills/batchy/batch.py latest-model
# → claude-fable-5  (as of writing)

# Pin into your env file
echo "export CLAUDE_MODEL=$(uv run ~/.claude/skills/batchy/batch.py latest-opus)" >> ~/.claude/env

The latest-* subcommands bypass the cache so you always get a fresh value.

Reproducibility vs currency. Auto-tracking is great for getting Anthropic improvements for free. It's bad if you run periodic evals or compare outputs across days — same prompt + different model = different output. Pin a specific id for those.

Cost note for auto-latest. Fable 5 is roughly 2× the cost of Opus 4.x (see cost table above). auto-latest could silently double per-job spend the next time Anthropic ships a Fable-tier model. auto-opus keeps you in the cheaper tier.

Fable 5 caveats

If you pin CLAUDE_MODEL=claude-fable-5 directly (or use auto-latest):

  • It costs ~2× Opus 4.x for the same job. The 50% Batch API discount still applies.
  • It supports only adaptive thinking (always on), not the extended thinking config. If you also set CLAUDE_THINKING=enabled, submissions will likely 400 — leave that env var unset when using Fable.
  • Max output is 128k (same as Opus 4.7+). The 300k batch-beta header is currently advertised only for Opus 4.6/4.7/4.8 and Sonnet 4.6 — Fable may or may not honor it.

File Locations

~/.claude/
├── env                          # ANTHROPIC_API_KEY (mode 600)
├── settings.json                # statusLine config
├── skills/
│   └── batchy/
│       ├── SKILL.md             # Skill definition (read by Claude Code)
│       └── batch.py             # CLI (invoked via `uv run`)
├── statusline.sh                # Status bar + cached poller
└── batches/
    ├── jobs.json                # Job registry
    ├── .poll_cache              # Last poll timestamp
    ├── .poll.lock               # Prevents concurrent polls
    └── results/
        ├── msgbatch_xxx.md      # Completed results
        └── msgbatch_xxx.meta.json

Cost Reference

Model Standard Batch (50% off)
Claude Fable 5 (claude-fable-5) $10 / $50 per 1M tokens $5 / $25
Claude Opus 4.7 / 4.8 (default: claude-opus-4-8) $5 / $25 per 1M tokens $2.50 / $12.50
Claude Opus 4.5 and earlier $15 / $75 per 1M tokens $7.50 / $37.50
Claude Sonnet 4.x $3 / $15 per 1M tokens $1.50 / $7.50
Claude Haiku 4.5 $1 / $5 per 1M tokens $0.50 / $2.50

(Input / Output per million tokens. Verified against Anthropic docs 2026-06-10.)

Typical turnaround: under 1 hour. Maximum: 24 hours.

Max output: Opus 4.7+ supports up to 128k tokens synchronously, and up to 300k on the Batch API with the output-300k-2026-03-24 beta header. The toolkit's default CLAUDE_MAX_TOKENS is 64k — override the env var to go higher.

Troubleshooting

"CLI not responding" or smoke test fails

# Test the CLI directly
uv run ~/.claude/skills/batchy/batch.py list

# Check if uv is installed
which uv

# Verify API key
grep ANTHROPIC_API_KEY ~/.claude/env

"No batch info in status bar"

# Check statusline config
jq '.statusLine' ~/.claude/settings.json

# Test statusline manually
echo '{}' | bash ~/.claude/statusline.sh

# Check jobs.json exists
cat ~/.claude/batches/jobs.json

"Job stuck in pending"

# Manual poll
uv run ~/.claude/skills/batchy/batch.py poll

# Check API status directly
source ~/.claude/env
curl -s -H "x-api-key: $ANTHROPIC_API_KEY" \
     -H "anthropic-version: 2023-06-01" \
     https://api.anthropic.com/v1/messages/batches/BATCH_ID

"Permission denied on env file"

chmod 600 ~/.claude/env

Architecture

  • CLI (skills/batchy/batch.py): Python script run by uv. Subcommands: submit, status, fetch, list, poll. Talks directly to the Anthropic Message Batches API.
  • Skill (skills/batchy/SKILL.md): Teaches Claude Code how and when to invoke the CLI. Loaded automatically when Claude Code starts.
  • Status Line (statusline.sh): Bash script that renders batch job counts in the Claude Code status bar and triggers background polling via curl+jq. Reads jobs.json directly — no Python involved on the fast path.
  • Jobs Registry (jobs.json): JSON file tracking all submitted batch jobs, their states, and result paths. Written atomically by the CLI and the statusline.

Credits

This is a fork of s2-streamstore/claude-batch-toolkit by Cristian C (cristian@s2.dev). The original design and implementation — Anthropic + Vertex backends, the cached status-line poller, the install/uninstall scripts, and the /batchy skill itself — is his work. This fork drops the Vertex backend; if you need it, use upstream.

The skill-only refactor (removing the MCP server layer in favor of direct CLI invocation, plus the safety hardening described in What this fork changes) is by Peter Dalinis.

License

MIT — see LICENSE. Copyright is held jointly by s2-streamstore (Cristian C) and Peter Dalinis.

About

Send non-urgent work to the Anthropic Batch API at 50% cost — directly from Claude Code

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Shell 55.2%
  • Python 44.8%