scripts/ai — companions to the fleet's ai-runner kit¶
Every model call a workflow here makes goes through uses: bamr87/bamr87/.github/actions/claude-run@main — the fleet's one AI step, versioned once in the bamr87/bamr87 hub and consumed by reference. This repo no longer carries the runner (scripts/ai/run.sh) or the composite action (.github/actions/claude-run/); it receives every fix on its next run with nothing to copy forward. The kit's contract, environment, and exit codes are documented in the hub: templates/ai-runner/README.md.
What stays here are the consumer-owned companions the hub runner probes for in $GITHUB_WORKSPACE and uses when present:
| File | Role | Required |
|---|---|---|
usage.rb |
Metering: one JSONL record per call (tokens, API-equivalent cost, model, status, CI context) into $AI_USAGE_DIR/records.jsonl (default $RUNNER_TEMP/ai-usage, outside the checkout). Prices come from _data/ai_pricing.yml. |
optional |
usage_report.rb |
End-of-job publisher: step summary, ai-usage-* artifact, sticky PR comment (marker <!-- lh-ai-usage -->). Run by the hub action's always() post-step. |
optional |
api_call.rb |
The single-shot Messages API fallback the runner uses when Claude Code is missing or fails. Stdlib only, self-contained. | optional |
../../_data/ai.yml |
model: (and max_tokens for the API fallback). The runner resolves --model > AI_MODEL > this file > the fleet default. |
recommended |
../../tools/unwrap-prose.py |
Post-run one-paragraph-per-line normalizer for markdown the agent changed. SCHEMA.md/CHANGELOG.md are always skipped; further exclusions go in a repo-root .prose-excludes, one extended regex per line. |
optional |
Without a companion the runner degrades honestly: no usage.rb means nothing is recorded (an inline emitter still treats is_error as a failure); no api_call.rb means a failed primary call exits 1 with "no scripts/ai/api_call.rb|py to fall back to".
Contract, in one line¶
Auth from the job env, OAuth first (CLAUDE_CODE_OAUTH_TOKEN, then ANTHROPIC_API_KEY); canonical AI_* environment with no repo prefix (AI_MODEL, AI_FORCE_API, AI_MAX_TURNS, AI_USAGE_DIR, AI_ROLE, AI_REPO_ROOT); exit 0 when the call ran or nothing was attempted (no auth — the documented no-op), exit 1 when a call was attempted and failed with no usable fallback, with the reason raised as a ::error:: annotation. The exit-code contract is pinned by the hub's templates/ai-runner/tests/contract.sh (stubbed claude, no network, no credentials) and run there on every change to the runner — not vendored here.
Calling it¶
- uses: bamr87/bamr87/.github/actions/claude-run@main
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # optional fallback
with:
agent: issue-triager # .claude/agents/<name>.md
prompt: "..."
tools: "Read,Grep,Glob,Bash(gh:*)"
Inputs: prompt, agent, tools, mcp, system, out, model, max-turns. Callers in this repo: issue-autopilot.yml (triage, verify, resolve) and visual-evidence-autogen.yml. Where a lane is the standard shape (gate on a *_ENABLED variable, run one agent, open one PR), the hub also offers the reusable bamr87/bamr87/.github/workflows/ai-lane.yml@main; the matrix-fed autopilot is deliberately not that shape.
Metering companions are kit files too¶
usage.rb, usage_report.rb, and api_call.rb are the same files lifehacker.dev runs. Fix them there first and copy forward (shasum parity is the check); never fork them here.