Verified against the live OpenRouter API and wrote the runbook to /workspace/solution.md.
Empirical proof captured in this session:
- deepseek-flash → HTTP 400, "deepseek-flash is not a valid model ID"
- deepseek/deepseek-v4-flash-0731 → HTTP 200, id=gen-…, choices: 1
- deepseek/deepseek-v4-flash-0731 is present in /api/v1/models
- The sed rewrite is idempotent (ran twice → 1 occurrence) and parses correctly.
gitreins judge tier2 stuck INCOMPLETE — 3× HTTP 400 from OpenRoutergitreins judge <ID> reports tier1 PASS but tier2 INCOMPLETE.HTTP 400 responses from
https://openrouter.ai/api/v1/chat/completions (one per tier2 LLM call).GITREINS_LLM_MODEL was set in ~/.hermes/.env to a bare alias:
GITREINS_LLM_MODEL=deepseek-flash
OpenRouter requires the full namespaced slug:
GITREINS_LLM_MODEL=deepseek/deepseek-v4-flash-0731
Environment variables override config defaults, so even when the gitreins
config file contains the correct slug, the exported/dotenv-loaded
GITREINS_LLM_MODEL wins. Every tier2 judge call therefore sends
"model": "deepseek-flash", and OpenRouter rejects it with:
{"error": {"message": "deepseek-flash is not a valid model ID", ...}, "code": 400}
Tier1 never touches the LLM (it runs the deterministic toolchain — node/pnpm/
gitreins), which is why tier1 passes while tier2 fails fast.
| Model sent | HTTP | Body |
|---|---|---|
deepseek-flash (bare alias) |
400 | deepseek-flash is not a valid model ID |
deepseek/deepseek-v4-flash-0731 (full slug) |
200 | gen-…, choices: 1 |
The distinction between "not a valid model ID" (400, bad model) and a normal 200 completion is the whole diagnosis: it is not auth, not quota, not network.
cp -a "$HOME/.hermes/.env" "$HOME/.hermes/.env.bak.$(date +%Y%m%d%H%M%S)"
GNU sed (Linux):
sed -i -E \
's|^([[:space:]]*GITREINS_LLM_MODEL[[:space:]]*=).*$|\1deepseek/deepseek-v4-flash-0731|' \
"$HOME/.hermes/.env"
macOS/BSD sed:
sed -i '' -E \
's|^([[:space:]]*GITREINS_LLM_MODEL[[:space:]]*=).*$|\1deepseek/deepseek-v4-flash-0731|' \
"$HOME/.hermes/.env"
The command is safe to run repeatedly: it rewrites the whole GITREINS_LLM_MODEL
line in place and never appends duplicates.
grep -n 'GITREINS_LLM_MODEL' "$HOME/.hermes/.env"
# expected: GITREINS_LLM_MODEL=deepseek/deepseek-v4-flash-0731
Make sure the corrected value is loaded into the process environment, then re-run:
set -a; . "$HOME/.hermes/.env"; set +a
gitreins judge <ID>
Tier2 should now complete instead of returning INCOMPLETE.
probe() {
local model="$1" code
code=$(curl -sS https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H 'Content-Type: application/json' \
-o /tmp/probe_resp.json -w '%{http_code}' \
-d "{\"model\":\"$model\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],\"max_tokens\":1}")
printf 'model=%-40s HTTP %s | ' "$model" "$code"
python3 -c 'import json;d=json.load(open("/tmp/probe_resp.json"));e=d.get("error");print("error="+(e or {}).get("message","-") if e else "ok id="+d.get("id","-"))'
}
probe "deepseek-flash" # expect HTTP 400, "not a valid model ID"
probe "deepseek/deepseek-v4-flash-0731" # expect HTTP 200, ok id=gen-...
Pass condition: the bare alias returns 400 and the full slug returns 200. This isolates the fault to the model string, independent of gitreins.
grep -E '^GITREINS_LLM_MODEL=' "$HOME/.hermes/.env"
# GITREINS_LLM_MODEL=deepseek/deepseek-v4-flash-0731
gitreins judge <ID>
Pass condition: tier2 reports a terminal verified/rejected verdict (not
INCOMPLETE), and the judge log contains zero HTTP 400 entries for
chat/completions.
If the environment has no /run/bunker, tier1 needs a usable toolchain on
PATH and a writable temp dir:
export PATH="$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
command -v node pnpm git gitreins # all four must resolve
export TMPDIR=/tmp
node/pnpm/gitreins makes tier1 fail before any LLM call.TMPDIR also breaks tier1; /tmp is the safe default.cp -a "$HOME/.hermes/.env" "$HOME/.hermes/.env.bak" && \
sed -i -E 's|^([[:space:]]*GITREINS_LLM_MODEL[[:space:]]*=).*$|\1deepseek/deepseek-v4-flash-0731|' "$HOME/.hermes/.env" && \
grep 'GITREINS_LLM_MODEL' "$HOME/.hermes/.env" && \
set -a && . "$HOME/.hermes/.env" && set +a && \
gitreins judge <ID>
Caveat: this sandbox has no ~/.hermes/.env and gitreins is a broken symlink, so I could not run the end-to-end gitreins judge step here. I verified the diagnosis and fix at the level that matters causally — the OpenRouter probe (400 vs 200) and the exact sed transform against a synthetic .env.
# Evidence - Problem class: gitreins-tier2-llm-400-openrouter - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-21T13:22:15.775Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "gitreins judge tier2 INCOMPLETE with 3x HTTP 400 to openrouter chat/completions while tier1 passes. Root cause: GITREINS_LLM_MODEL in ~/.hermes/.env held a bare alias (deepseek-flash) instead of the full OpenRouter slug (deepseek/deepseek-v4-flash-0731); env overrides config defaults, so every judge LLM call 400s. Diagnosis: probe both slugs with a 1-token completion (400 not-a-valid-model-ID vs 200). Fix: sed the .env line to the full slug, re-run gitreins judge <ID> (idempotent). Bunkers without /run/bunker need PATH node+pnpm+gitreins and TMPDIR=/tmp for tier1.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-tier2-llm-400-openrouter", "provider": "openrouter", "solved_at": "2026-09-21T13:22:15.775Z", "version": ""}