Setup and API keys
Most of judgekeeper needs no key at all: the demo, check on a spreadsheet and the framework imports work offline. You need a key only when judgekeeper calls a model for you, with --runner anthropic or --runner openai.
Two words first
- An API key is a secret string, like a password, that lets a program use your account with a model provider such as Anthropic or OpenAI. Anyone who has it can spend your money.
- An environment variable is a named setting that your terminal or CI system hands to every program it starts. judgekeeper reads keys only from environment variables, so a key never has to be typed into a command or saved in a file.
On your laptop
-
Create a key just for judgekeeper
In your provider's console, make a new key and give it a low spending limit. If it ever leaks, little can be spent and you can delete it without breaking anything else.
-
Put it in your shell profile
Your shell profile is a file your terminal reads when it starts:
~/.zshrcon a Mac, usually~/.bashrcon Linux. Open it in a text editor and add one line, with your key in place of the dots:export ANTHROPIC_API_KEY=...For OpenAI the name is
OPENAI_API_KEY. This file lives in your home folder, outside any project, so it never ends up in a repository. -
Open a new terminal and check
The profile is read when a terminal starts. This prints
setif the key is there, without showing the key:echo ${ANTHROPIC_API_KEY:+set} -
Run a judge
judgekeeper judge anchors.jsonl --runner anthropic --model claude-haiku-4-5-20251001 --prompt prompts/single.md --runs 3 --out runs/haiku/
A key under another name
Any variable name works. Tell judgekeeper the name with --api-key-env. It takes a name, never the key itself:
export OPENROUTER_API_KEY=...
judgekeeper judge anchors.jsonl --runner openai --model "$JUDGE_MODEL" --base-url https://openrouter.ai/api/v1 --api-key-env OPENROUTER_API_KEY --prompt prompts/single.md --runs 3 --out runs/openrouter/Other providers and local models
--base-url sends the requests to another address instead of the provider's default. Use --runner openai for any server that speaks the OpenAI format:
| Where your model runs | What to pass |
|---|---|
| Azure OpenAI | --base-url with your resource's v1 endpoint, and --api-key-env naming the variable that holds your Azure key |
| OpenRouter | --base-url https://openrouter.ai/api/v1 --api-key-env OPENROUTER_API_KEY |
| AWS Bedrock or Google Vertex | Run a gateway such as LiteLLM in front of them, and pass its address with --base-url |
| Ollama on your laptop | --base-url http://localhost:11434/v1, no key needed |
judgekeeper judge anchors.jsonl --runner openai --model "$JUDGE_MODEL" --base-url http://localhost:11434/v1 --prompt prompts/single.md --runs 3 --out runs/local/The address goes into the judge's fingerprint. The same model behind a different address counts as a different judge.
In GitHub Actions
-
Store the key as a repository secret
On GitHub, open your repository, then Settings, Secrets and variables, Actions, New repository secret. Name it
ANTHROPIC_API_KEYand paste the key there. GitHub hides it in logs. -
Pass it as
env, never as an inputThe workflow hands the secret to the step as an environment variable. The judgekeeper action has no input for a key, on purpose.
-
Copy the example workflow
Save this as
.github/workflows/judge-gate.ymlin your repository and change the paths. It runs every Monday, to catch a silent model update, and on pull requests that change the judge. It is the same file as docs/examples/workflows/judge-gate.yml.# Copy to .github/workflows/judge-gate.yml in your repo and adjust the paths. # Before the first run: `judgekeeper freeze evals/anchors.jsonl`, judge and validate once, # then `judgekeeper baseline set reports/report.json` and commit .judgekeeper/baseline.json. name: judge gate on: # Weekly: the provider can change what a model id serves without any change in your repo. # A scheduled run catches judge drift (a new snapshot, a silent update) between PRs. schedule: - cron: "17 6 * * 1" # Mondays 06:17 UTC # Pull requests that change the judge itself: its prompt, the anchor set it is measured # against, the committed baseline or the gate thresholds. Other PRs cannot move the judge, # so they do not pay for judge calls. pull_request: paths: - "prompts/judge.md" - "evals/anchors.jsonl" - "evals/anchors.manifest.json" - ".judgekeeper/baseline.json" - "judgekeeper.toml" permissions: contents: read jobs: gate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: judgekeeper/judgekeeper@main # pin to a release tag or commit sha env: # Keys come from repository secrets, never from action inputs. ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} with: anchors: evals/anchors.jsonl runner: anthropic model: claude-haiku-4-5-20251001 prompt: prompts/judge.md runs: 3 flaky-as: pass # FLAKY is reported in the summary but does not fail the build # Weekly only: the gate job above has just re-judged the frozen anchor set. Compare those # verdicts item by item with the baseline's to tell judge drift from a change in your system, # even when the declared fingerprint is identical (a silent provider-side update). # The baseline must carry per-item verdicts (`items` in report.json, schema_version 2): if it # predates them, re-run `validate` on the baseline runs and `baseline set` again. attribute: needs: gate if: always() && github.event_name == 'schedule' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" # pin @main to a release tag or commit sha, as for the action above - run: pip install "judgekeeper @ git+https://github.com/judgekeeper/judgekeeper@main" - uses: actions/download-artifact@v4 with: name: judgekeeper-gate # the gate action's artifact-name path: judgekeeper-out - name: Attribute run: | set +e judgekeeper attribute judgekeeper-out/report.json --baseline .judgekeeper/baseline.json code=$? cat judgekeeper-out/attribution.md >> "$GITHUB_STEP_SUMMARY" exit $code # 0 STABLE, 6 JUDGE_DRIFT, 7 SYSTEM_CHANGE, 2 usage, 3 anchors changed
In cloud coding sessions
Claude Code on the web runs your session on a machine in the cloud. Setting a key there takes three steps:
-
Use the Environment variables box
Open the environment's settings and add the key in the Environment variables box, under a name of your own:
JUDGEKEEPER_ANTHROPIC_KEY=...Do not use
ANTHROPIC_API_KEYthere: the session itself uses Anthropic's variables. -
Point judgekeeper at that name and at Anthropic's address
Those machines set
ANTHROPIC_BASE_URLto an internal proxy, so pass Anthropic's own address too:judgekeeper judge anchors.jsonl --runner anthropic --model claude-haiku-4-5-20251001 --prompt prompts/single.md --api-key-env JUDGEKEEPER_ANTHROPIC_KEY --base-url https://api.anthropic.com --runs 3 --out runs/haiku/ -
Start a new session
Changes to the environment apply to new sessions only. A session that was already running does not see the key.
Where a key must never go
- Files in your repository, including config files and
.envfiles that get committed. Git keeps history: a key committed once stays findable. - Command-line flags. Commands are saved in your shell history and visible to other programs. judgekeeper has no flag that takes a key.
- Prompts, for the judge or for a coding agent.
- Chat messages, issues and screenshots.
If a key leaks, delete it in your provider's console straight away and make a new one.
Good habits
- Use a dedicated key for judgekeeper, with a low spending limit.
- Count the calls before a run: items times runs, and twice that for two-answer comparisons. judgekeeper asks you to confirm with
--yesabove 1,000 calls for your own judge functions. - judgekeeper replaces anything that looks like a key with
[REDACTED]in everything it prints or writes. That is a safety net, not a reason to paste keys anywhere.