Problem class: gitreins-tier1-register-appendix-stale-after-foreman-board-row-collapse
The solution is saved at /workspace/solution.md. I verified the tier-2 half live against OpenRouter/DeepSeek (401 with the OpenRouter key on the DeepSeek base URL; 200 with the same key on https://openrouter.ai/api/v1 and slug deepseek/deepseek-v4-flash-0731), and modeled the tier-1 stale-register failure/fix with a minimal reproduction.
Problem class: gitreins-tier1-register-appendix-stale-after-foreman-board-row-collapse
TL;DR
# 1) Tier 1 — settle the register first, commit, push.
python3 scripts/gen-disposition-register.py --write
git diff --stat && git diff # must show only the collapsed row(s) + beta-111 timestamp/gitHead
git status --short # add exactly those files
git commit -m "chore(register): regenerate disposition appendix after board row collapse"
git push origin HEAD
# 2) Tier 2 — one retry, with endpoint/key/model aligned to OpenRouter.
GITREINS_LLM_BASE_URL=https://openrouter.ai/api/v1 \
GITREINS_LLM_API_KEY="$OPENROUTER_API_KEY" \
GITREINS_LLM_MODEL=deepseek/deepseek-v4-flash-0731 \
gitreins judge <TASK_ID>
Two independent failures were racing on the same release gate.
.coding-hermes/board/tasks.jsonl (one JSON object per task).scripts/gen-disposition-register.py) is derived from that board..coding-hermes/board/tasks.jsonl. The generated register was not
regenerated in that commit.tests-leg / release-gate-matrix check regenerates the appendix
in memory and compares it to the checked-in register. It fails closed with:text
validation failed: generated appendix differs from register
So tier 1 is not a code bug. It is a derived-artifact commit-ordering bug: the board mutation and the register regeneration were committed separately.
GITREINS_LLM_API_KEY holds an OpenRouter key (sk-or-v1-...).GITREINS_LLM_BASE_URL points at https://api.deepseek.com.text
HTTP error (attempt 1/3): 401 Client Error: Unauthorized for
url: https://api.deepseek.com/chat/completions
...
RuntimeError: LLM request failed after 3 attempts
gitreins builds its endpoint as <base_url>/chat/completions, so the key and
the base URL must belong to the same provider. Fixing tier 1 first matters:
the judge retry runs tier-1 guards again, so retrying against a still-stale
register just burns the one retry.
Run from the repository root.
cd "$(git rev-parse --show-toplevel)"
# The board mutation must already be committed; there should be no uncommitted
# board writer in flight.
git status --short .coding-hermes/board/tasks.jsonl # expect: no output
git log -1 --stat -- .coding-hermes/board/tasks.jsonl # the surgery commit
# The generator must exist at the documented path.
test -f scripts/gen-disposition-register.py && echo "generator present"
If a scheduled foreman tick could write the board while you regenerate, either pause it for the duration of this procedure or confirm no tick is running. Regenerating against a board that changes underneath you is exactly how the register gets stale again.
t636)bash
python3 scripts/gen-disposition-register.py --write
Inspect the diff. It must contain only:
the affected collapsed row(s) in the generated register, and
timestamp + gitHead regeneration in the beta-111
evidence pack (usually under docs/dogfood/evidence/beta-111-*.json).bash
git status --short
git diff --stat
git diff -- . ':!.coding-hermes/board/tasks.jsonl'
Expected shape:
text
M <register-file> # only the collapsed task row(s)
M docs/dogfood/evidence/beta-111-*.json # only "timestamp" and "gitHead"
Abort if the diff touches any other task row, reorders the appendix, changes unrelated statuses, or edits files outside the register and the beta-111 evidence pack. That means the board has other unsynced edits and must be settled first.
bash
git add <register-file> docs/dogfood/evidence/beta-111-*.json
git commit -m "chore(register): regenerate disposition appendix after board row collapse"
git push origin HEAD
bash
python3 scripts/gen-disposition-register.py --write
git diff --exit-code -- <register-file> \
|| { echo "register still changing after two writes — investigate"; exit 1; }
If the script exposes a check mode (commonly --check), use it too:
bash
python3 scripts/gen-disposition-register.py --check
bash
gitreins guard
# or the focused test, e.g.
python3 -m pytest tests/ -q -k release_gate_matrix
t615)Use a single, explicit provider tuple. Prefer an inline environment so the override only applies to the retry:
GITREINS_LLM_BASE_URL=https://openrouter.ai/api/v1 \
GITREINS_LLM_API_KEY="$OPENROUTER_API_KEY" \
GITREINS_LLM_MODEL=deepseek/deepseek-v4-flash-0731 \
gitreins judge <TASK_ID>
Equivalent exported form:
export GITREINS_LLM_BASE_URL=https://openrouter.ai/api/v1
export GITREINS_LLM_API_KEY="${OPENROUTER_API_KEY:?OPENROUTER_API_KEY is not set}"
export GITREINS_LLM_MODEL=deepseek/deepseek-v4-flash-0731
# Only needed by code paths that force provider detection:
# export GITREINS_LLM_PROVIDER=openai
gitreins judge <TASK_ID>
Notes:
sk-or-v1-...). The variable being wrong is not
the problem — the base URL and model slug are.deepseek/deepseek-v4-flash-0731 is a valid OpenRouter slug and accepts the
thinking: {"type": "disabled"} field that gitreins adds for DeepSeek
models.https://openrouter.ai/api/v1 is the OpenAI-compatible base URL; gitreins
appends /chat/completions.# Register is deterministic: a second write is a no-op.
python3 scripts/gen-disposition-register.py --write
git diff --exit-code -- <register-file>
# The commit contains only the register + beta-111 evidence.
git log -1 --stat
git show --name-only --pretty=format: HEAD
# Tier 1 passes.
gitreins guard
# or: python3 -m pytest tests/ -q -k release_gate_matrix
Expected:
git diff --exit-code exits 0.git show --name-only HEAD lists only the register and the beta-111 evidence
file.release-gate-matrix reports PASS; no
generated appendix differs from register.Wrong endpoint with the OpenRouter key — reproduces the 401:
curl -sS -o /dev/null -w '%{http_code}\n' \
https://api.deepseek.com/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"hi"}]}'
# 401
The same key against the correct endpoint — passes:
curl -sS -o /dev/null -w '%{http_code}\n' \
https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"deepseek/deepseek-v4-flash-0731","messages":[{"role":"user","content":"say ok"}],"max_tokens":5}'
# 200
gitreins' own client, with the fix applied, behaves the same way:
# verified with the repo's engine/llm.py
LLMClient(base_url="https://api.deepseek.com",
api_key="<sk-or-v1 key>", model="deepseek-chat").chat(...)
# -> 401 after retries -> RuntimeError: LLM request failed after 3 attempts
LLMClient(base_url="https://openrouter.ai/api/v1",
api_key="<sk-or-v1 key>",
model="deepseek/deepseek-v4-flash-0731").chat(...)
# -> success
After the judge retry:
# Judge should print a PASS verdict and exit 0.
GITREINS_LLM_BASE_URL=https://openrouter.ai/api/v1 \
GITREINS_LLM_API_KEY="$OPENROUTER_API_KEY" \
GITREINS_LLM_MODEL=deepseek/deepseek-v4-flash-0731 \
gitreins judge <TASK_ID> | tee /tmp/judge-retry.log
grep -E 'PASS|FAIL|401|Unauthorized' /tmp/judge-retry.log
# The new verdict is persisted.
ls -t .gitreins/history/ | head
# Working tree stays clean.
git status --short
Expected: PASS, no 401, no Unauthorized, clean tree.
.coding-hermes/board/tasks.jsonl must regenerate and stage the register in
the same commit. A pre-commit hook or CI step can enforce this:bash
python3 scripts/gen-disposition-register.py --write
git diff --exit-code -- <register-file>
The check is cheap and catches the exact pending-twin class before push.
CI drift guard for the register. Add a release-gate-matrix job that
regenerates the appendix and fails if the checked-in copy differs. This is the
same comparison tier 1 already performs; run it on push and on
.coding-hermes/board/tasks.jsonl path changes.
Keep provider tuples paired. Treat base URL + API key + model slug as one
config object. Never point sk-or-v1-* at api.deepseek.com, and never point
a DeepSeek sk-* key at OpenRouter. Add a startup assertion in the scheduler
that refuses to launch the judge when the key prefix and base URL host do not
match.
Pin the OpenRouter route. If the judge should always use OpenRouter,
encode it in .gitreins/config.yaml (or the scheduler env) rather than in an
ad-hoc retry shell:
yaml
defaults:
model: deepseek/deepseek-v4-flash-0731
with GITREINS_LLM_BASE_URL=https://openrouter.ai/api/v1 in the durable env.
Tier 1 is restored by regenerating the derived register from the already-settled
board and committing that single artifact before any retry, so the
release-gate-matrix comparison is comparing against a register that matches the
board. Tier 2 is restored by giving the judge a coherent provider tuple — an
OpenRouter key, the OpenRouter base URL, and a valid OpenRouter model slug — so
the request is authenticated by the provider that actually issued the credential.
Retrying only once after tier 1 is settled matches the successful t636
precedent and avoids masking a still-stale register behind repeated 401 retries.
# Evidence - Problem class: gitreins-tier1-register-appendix-stale-after-foreman-board-row-collapse - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-16T02:58:44.616Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Foreman-direct board row surgery (stale pending-twin collapse, commit touching only .coding-hermes/board/tasks.jsonl) staleness the generated disposition register: tier1 tests-leg fails release-gate-matrix with validation failed: generated appendix differs from register; tier2 concurrently 401s because GITREINS_LLM_API_KEY (sk-or-v1 openrouter key) points at api.deepseek.com base URL. Remedy: (1) settle tier1 first per t636 - regen scripts/gen-disposition-register.py --write on the settled board, verify the diff is exactly the affected rows (plus benign timestamp+gitHead regen in beta-111 evidence pack), commit and push BEFORE the retry; (2) retry judge once with env overrides GITREINS_LLM_BASE_URL=https://openrouter.ai/api/v1 + the openrouter key + slug deepseek/deepseek-v4-flash-0731 per t615. One retry sufficed in the t636 precedent when tier1 was settled first.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-tier1-register-appendix-stale-after-foreman-board-row-collapse", "provider": "openrouter", "solved_at": "2026-09-16T02:58:44.616Z", "version": ""}