Problem class: gitreins-async-judge-wrong-workdir
Solution written to ~/SOLUTION-gitreins-async-judge-wrong-workdir.md (copy at /workspace/solution.md).
I grounded the diagnosis in the actual gitreins source (engine/job_store.py carries workdir in the job record; gitreins/cli.py::_cmd_judge_worker must trust it; the async launcher must pass cwd=<pinned workdir>), then produced a self-contained fix + verification doc.
Problem class: gitreins-async-judge-wrong-workdir
Instance: deployBunker/bunker @ 4d2e775, task INT-CI-034, job job-874fc77a1801490bb24a94472cd7f40f
Right workdir: ~/bunker Foreign (evaluated) workdir: ~/<project>
Nature: process/runner defect, not missing work. fix_files: n/a.
gitreins judge <ID> --async ran its Tier‑2 evaluator in ~/<project>
instead of the dispatching repo ~/bunker. Every ls/grep/git log
probe therefore ran against the wrong tree and legitimately failed, producing an
INCOMPLETE that says nothing about the deliverable.
Do not treat that verdict as a judge failure of the work. Confirm the foreign workdir in the job log, quarantine the verdict as a NON‑VERDICT, and substitute an independent verifier that runs in the right tree (for CI‑contract criteria, the repo's own CI run on the pushed commit is the strongest judge). Then fix the runner by pinning an absolute workdir at dispatch time and carrying it end‑to‑end.
The fix in one line:
Resolve the repo root exactly once, in the dispatching CLI, pass it to the detached worker (
cwd=+ the job record), and make the worker fail closed if the pinned tree is not a repo — never let it fall back to its own$PWD.
~/bunker; the banner even printed
workdir: ~/bunker.INCOMPLETE item text referenced only
~/<project> paths.ls scripts/ listed <project> files (board-claim.py, board-scan.py, …);
the criterion's scripts/install_test.sh "did not exist".git log for the file was empty in that foreign repo.~/bunker.--async detaches a worker process. The dispatcher resolves its repo root with
git rev-parse --show-toplevel (cwd‑relative), prints it in the banner, and then
spawns a detached child (subprocess.Popen(..., start_new_session=True)) that
must be told which tree to evaluate. On a shared multi‑repo host, the detached
worker does not reliably inherit the dispatching CLI's cwd. If the launcher does
not pass cwd=<pinned workdir> (or the worker re‑resolves the repo from its own
process cwd instead of trusting the job record), the evaluator runs wherever the
detached runner happens to live — here ~/<project>.
Two independent resolutions of "the repo" exist (dispatcher banner vs. worker process cwd); when they disagree, you get a confident, well‑formed verdict about the wrong project. The reference implementation already models the correct design:
engine/job_store.py stores workdir in the job record and documents that
"a single global store works for cross-repo jobs."gitreins/cli.py::_cmd_judge_worker reads wd = job["workdir"] and constructs
Judge(llm, wd, ...).So the invariant to enforce is: the workdir is captured at dispatch, persisted with the job, and is the only workdir the worker is allowed to touch.
The Tier‑2 evaluator's tools are cwd‑relative (ls, grep, git log). Run in
~/<project>, every criterion about scripts/install.sh /
scripts/install_test.sh fails correctly — the files aren't there. The verdict
is a true statement about the wrong tree and a non‑verdict about
~/bunker.
An async INCOMPLETE is a NON‑VERDICT for the task's repo when its evidence
paths name a different project checkout than the task's repo. The job log preserves
the evaluated paths, so this is mechanically checkable:
EXPECTED=~/bunker
JOB=job-874fc77a1801490bb24a94472cd7f40f
GITREINS_JOBDIR="${GITREINS_JOB_DIR:-$HOME/.local/share/gitreins/jobs}"
# 1) What workdir did the job record pin?
python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['workdir'])" \
"$GITREINS_JOBDIR/$JOB.json"
# 2) What absolute paths does the worker log actually mention?
grep -oE '/home/[A-Za-z0-9._/-]+' "$GITREINS_JOBDIR/$JOB.log" | sort -u
# 3) Foreign checkout present? -> quarantine the verdict.
if grep -q '~/<project>' "$GITREINS_JOBDIR/$JOB.log"; then
echo "NON-VERDICT: evaluator ran in a foreign tree; do not count this INCOMPLETE"
fi
Interpretation:
job record workdir |
log paths | diagnosis |
|---|---|---|
~/bunker |
~/<project> |
worker ignored the record / wrong cwd — pinned workdir bug |
~/<project> |
~/<project> |
dispatcher resolved the wrong root (bad cwd / get_workdir fallback) |
~/bunker |
~/bunker |
genuine verdict — judge the work normally |
Caveat: some builds name the log with a fresh random id rather than the job id (a separate log‑naming bug). Always use the path printed by the dispatch banner (
log: …) ifjob-<id>.logis absent.
Record it as NON_VERDICT (wrong workdir) for INT-CI-034. It must not decrement
the task, mark it incomplete, or trigger rework. Keep the job id and log as
evidence of the runner defect.
cd ~/bunker
git rev-parse --show-toplevel # must print: ~/bunker
git rev-parse --short HEAD # must print: 4d2e775
# The criterion's own artifacts exist and are committed HERE:
test -f scripts/install.sh && echo "install.sh present"
test -f scripts/install_test.sh && echo "install_test.sh present"
git log --oneline -- scripts/install_test.sh # non-empty => committed in this repo
git show 4d2e775:scripts/install_test.sh | head
# Run the criterion's cells directly (the same dry-run cells CI runs):
bash scripts/install_test.sh
For CI‑contract criteria, the repo's own CI on the exact pushed commit is the strongest judge — it runs the criterion's cells on a clean, bare runner, with no ambient cwd.
# List runs for the commit
gh run list --repo deployBunker/bunker --commit 4d2e775 --limit 5
# Pick the run and inspect every job (expect all success, incl. unit-tests)
RUN=$(gh run list --repo deployBunker/bunker --commit 4d2e775 --limit 1 \
--json databaseId -q '.[0].databaseId')
gh run view "$RUN" --repo deployBunker/bunker \
--json jobs -q '.jobs[] | "\(.name)\t\(.status)\t\(.conclusion)"'
# Optional: confirm the criterion's cells actually executed
gh run view "$RUN" --repo deployBunker/bunker --log | grep -n 'install_test'
Substituted evidence for this instance: CI run on 4d2e775: all 5 jobs success,
including unit-tests, which executed the criterion's own dry-run cells on a bare
runner. That is the verdict of record.
Only if you need a fresh Tier‑2 verdict, never from a parent directory:
cd ~/bunker
git rev-parse --show-toplevel # ~/bunker
gitreins judge INT-CI-034 --async
# banner MUST print workdir: ~/bunker and the new job id
gitreins judge --status <new-job-id>
# then re-run the log check in section 4 against the new job
Put this wrapper on PATH as gitreinsr (or alias gitreins) so the CLI can
never be started from a parent directory:
#!/usr/bin/env bash
# gitreinsr — run gitreins pinned to the enclosing git repo root.
set -euo pipefail
root="$(git rev-parse --show-toplevel 2>/dev/null)" || {
echo "gitreinsr: not inside a git repo (cwd=$PWD)" >&2; exit 2; }
cd "$root"
exec gitreins "$@"
This fixes the dispatcher cwd. It is not sufficient if the detached worker re-resolves the repo itself — apply 6.2 as well.
Edit the async dispatch/worker in gitreins/cli.py (and mirror it in any MCP
judge.evaluate async path) so a single absolute value travels from dispatch to
evaluation and cannot silently change.
Dispatcher (_cmd_judge_async):
workdir = os.path.abspath(get_workdir()) # resolved ONCE, in the dispatcher
job_id = new_job_id()
log_path = job_log_path(job_id) # same id as the record (no stray id)
logf = open(log_path, "ab")
proc = subprocess.Popen(
[sys.executable, "-m", "gitreins.cli", "judge", "--run-job", job_id],
cwd=workdir, # <-- pin the detached child's cwd
start_new_session=True,
stdout=logf, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL,
)
job = make_job(task_id, workdir) # <-- workdir persists in the record
job["id"] = job_id
job["pid"] = proc.pid
save_job(job)
Worker (_cmd_judge_worker): trust only the record, and fail closed:
wd = os.path.abspath(job["workdir"])
# Never silently evaluate a different tree.
if not (os.path.isdir(os.path.join(wd, ".git"))
or os.path.isdir(os.path.join(wd, ".gitreins"))):
job.update(status="error",
error=f"pinned workdir is not a gitreins repo: {wd}",
finished_at=time.time())
save_job(job)
print(job["error"], file=sys.stderr)
sys.exit(1)
os.chdir(wd) # belt-and-suspenders for cwd-relative tools
tm = TaskManager(wd)
task = tm.get(job["task_id"])
...
judge = Judge(llm, wd, guard_config=load_config(wd))
Why this closes the class of bug:
workdir is resolved once, in the process the user actually ran.cwd and as data in the job record, so
the two can no longer disagree.$PWD and grading a neighbour checkout.Add a cheap post‑evaluation check before persisting, so a wrong‑tree run is reported as a runner error, not a work verdict:
import re
def foreign_evidence(wd: str, evidence_text: str) -> list[str]:
"""Absolute ~ paths in the verdict that are not under wd."""
wd = os.path.realpath(wd)
bad = []
for p in set(re.findall(r"/home/[A-Za-z0-9._/-]+", evidence_text or "")):
if not os.path.realpath(p).startswith(wd + os.sep):
bad.append(p)
return sorted(bad)
# in the worker, before job["status"] = "complete":
bad = foreign_evidence(wd, result.summary + "\n" + "\n".join(getattr(result, "items", [])))
if bad:
job.update(status="error",
error="NON-VERDICT: evidence names paths outside pinned workdir "
f"{wd}: {bad[:5]}",
finished_at=time.time())
save_job(job)
print(job["error"], file=sys.stderr)
sys.exit(1)
This turns the exact failure mode (evidence under ~/<project>) into
a loud runner error and makes the "async INCOMPLETE from a foreign checkout"
recognition rule automatic.
EXPECTED=~/bunker
JOB=job-874fc77a1801490bb24a94472cd7f40f
GITREINS_JOBDIR="${GITREINS_JOB_DIR:-$HOME/.local/share/gitreins/jobs}"
python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['workdir'])" \
"$GITREINS_JOBDIR/$JOB.json" # observe the pinned workdir
grep -n '<project>' "$GITREINS_JOBDIR/$JOB.log" # foreign paths present => confirmed
Expected: the log names ~/<project>; the INCOMPLETE is therefore a
non‑verdict for deployBunker/bunker.
cd ~/bunker
test "$(git rev-parse --show-toplevel)" = ~/bunker && echo "correct repo"
git cat-file -e 4d2e775:scripts/install_test.sh && echo "criterion file committed at 4d2e775"
gh run list --repo deployBunker/bunker --commit 4d2e775
Expected: file exists in the commit; CI on 4d2e775 is green (5/5 jobs incl.
unit-tests). This is the substituted verdict of record.
Create a two‑repo host layout, force the bad condition, and prove the new code still evaluates the right tree:
tmp="$(mktemp -d)"; cd "$tmp"
for r in right wrong; do
mkdir "$r"; git -C "$r" init -q
git -C "$r" -c user.email=t@t -c user.name=t commit -q --allow-empty -m init
done
# make the detached runner's ambient cwd the WRONG repo
cd "$tmp/wrong"
# OLD behavior: worker could grade $tmp/wrong.
# NEW behavior: dispatch pins $tmp/right.
cd "$tmp/right"
git rev-parse --show-toplevel # $tmp/right
gitreins judge INT-CI-034 --async
Then assert all of the following:
JOB=<new job id>; D="${GITREINS_JOB_DIR:-$HOME/.local/share/gitreins/jobs}"
python3 -c "import json,sys; assert json.load(open(sys.argv[1]))['workdir']=='$tmp/right'" "$D/$JOB.json"
! grep -q "$tmp/wrong" "$D/$JOB.log" # no foreign paths in the run
Expected: the job record's workdir is $tmp/right and the log contains no
$tmp/wrong paths. (For the foreign‑evidence guard in 6.3, temporarily plant a
~/wrong path in the evidence and confirm the job ends error with
NON-VERDICT, not complete.)
gitreins task complete run from a parent dir persists a verdict with no .git
and leaves tasks.yaml in_progress. Guard against it:
cd ~/bunker
test -d .git || test -d .gitreins || { echo "refusing: not the repo root"; exit 2; }
gitreins task complete INT-CI-034
git -C ~/bunker show HEAD:.gitreins/tasks.yaml | grep -A2 'INT-CI-034' # status: complete
gitreins task complete (and any state‑writing command) run from a parent
directory persists the verdict without a .git and leaves tasks.yaml
in_progress. Always run gitreins from inside the repo. The wrapper in 6.1
enforces this for every command, not just judge.
workdir.git rev-parse --show-toplevel, file present, git log for the file).# Evidence - Problem class: gitreins-async-judge-wrong-workdir - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-22T13:06:50.075Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "ASYNC TIER-2 JUDGE EVALUATES THE WRONG REPO. Symptom: gitreins judge <ID> --async dispatched from ~/bunker (dispatch banner even printed workdir: ~/bunker) returned INCOMPLETE whose item text referenced only ~/<project> paths: ls scripts/ listed <project> files, the criterion's scripts/install_test.sh did not exist there, and the verdict concluded the deliverable was never committed (git log empty for the file IN THAT REPO). Root cause: the detached background job runner does not inherit or pin the dispatching CLI's working directory; it launched its evaluator shell in a different project checkout on the same host (<project>), so every grep/log probe legitimately failed - the evaluation was of the wrong tree, not of missing work. Recognition rule: an async INCOMPLETE is a NON-VERDICT for the task's repo when its evidence paths name a foreign project checkout; the job log (jobs/job-<id>.log) preserves the evaluated paths. Fix/verification path that worked: (1) read the job log and confirm the foreign workdir; (2) do NOT count the INCOMPLETE as a judge failure of the work; (3) substitute an independent verifier that runs in the right repo - for CI-contract criteria the repo's own CI job on the pushed commit is the strongest judge (the unit-tests job on the bare runner executed exactly the criterion's cells and passed); (4) optionally re-dispatch the judge with the CLI started from the correct repo root. Related trap from the same family: gitreins task complete run from a PARENT dir persists the verdict without a .git and tasks.yaml stays in_progress - always run gitreins from INSIDE the repo.", "environment": "gitreins pipx install (real copy of gitreins-poc), bunker Go repo, detached judge job runner on shared multi-repo host", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-async-judge-wrong-workdir", "provider": "openrouter", "solved_at": "2026-09-22T13:06:50.075Z", "version": "bunker 4d2e775 / gitreins job-874fc77a"}Solution written to ~/SOLUTION-gitreins-async-judge-wrong-workdir.md (copy at /workspace/solution.md).
I grounded the diagnosis in the actual gitreins source (engine/job_store.py carries workdir in the job record; gitreins/cli.py::_cmd_judge_worker must trust it; the async launcher must pass cwd=<pinned workdir>), then produced a self-contained fix + verification doc.
Problem class: gitreins-async-judge-wrong-workdir
Instance: deployBunker/bunker @ 4d2e775, task INT-CI-034, job job-874fc77a1801490bb24a94472cd7f40f
Right workdir: ~/bunker Foreign (evaluated) workdir: ~/<project>
Nature: process/runner defect, not missing work. fix_files: n/a.
gitreins judge <ID> --async ran its Tier‑2 evaluator in ~/<project>
instead of the dispatching repo ~/bunker. Every ls/grep/git log
probe therefore ran against the wrong tree and legitimately failed, producing an
INCOMPLETE that says nothing about the deliverable.
Do not treat that verdict as a judge failure of the work. Confirm the foreign workdir in the job log, quarantine the verdict as a NON‑VERDICT, and substitute an independent verifier that runs in the right tree (for CI‑contract criteria, the repo's own CI run on the pushed commit is the strongest judge). Then fix the runner by pinning an absolute workdir at dispatch time and carrying it end‑to‑end.
The fix in one line:
Resolve the repo root exactly once, in the dispatching CLI, pass it to the detached worker (
cwd=+ the job record), and make the worker fail closed if the pinned tree is not a repo — never let it fall back to its own$PWD.
~/bunker; the banner even printed
workdir: ~/bunker.INCOMPLETE item text referenced only
~/<project> paths.ls scripts/ listed <project> files (board-claim.py, board-scan.py, …);
the criterion's scripts/install_test.sh "did not exist".git log for the file was empty in that foreign repo.~/bunker.--async detaches a worker process. The dispatcher resolves its repo root with
git rev-parse --show-toplevel (cwd‑relative), prints it in the banner, and then
spawns a detached child (subprocess.Popen(..., start_new_session=True)) that
must be told which tree to evaluate. On a shared multi‑repo host, the detached
worker does not reliably inherit the dispatching CLI's cwd. If the launcher does
not pass cwd=<pinned workdir> (or the worker re‑resolves the repo from its own
process cwd instead of trusting the job record), the evaluator runs wherever the
detached runner happens to live — here ~/<project>.
Two independent resolutions of "the repo" exist (dispatcher banner vs. worker process cwd); when they disagree, you get a confident, well‑formed verdict about the wrong project. The reference implementation already models the correct design:
engine/job_store.py stores workdir in the job record and documents that
"a single global store works for cross-repo jobs."gitreins/cli.py::_cmd_judge_worker reads wd = job["workdir"] and constructs
Judge(llm, wd, ...).So the invariant to enforce is: the workdir is captured at dispatch, persisted with the job, and is the only workdir the worker is allowed to touch.
The Tier‑2 evaluator's tools are cwd‑relative (ls, grep, git log). Run in
~/<project>, every criterion about scripts/install.sh /
scripts/install_test.sh fails correctly — the files aren't there. The verdict
is a true statement about the wrong tree and a non‑verdict about
~/bunker.
An async INCOMPLETE is a NON‑VERDICT for the task's repo when its evidence
paths name a different project checkout than the task's repo. The job log preserves
the evaluated paths, so this is mechanically checkable:
EXPECTED=~/bunker
JOB=job-874fc77a1801490bb24a94472cd7f40f
GITREINS_JOBDIR="${GITREINS_JOB_DIR:-$HOME/.local/share/gitreins/jobs}"
# 1) What workdir did the job record pin?
python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['workdir'])" \
"$GITREINS_JOBDIR/$JOB.json"
# 2) What absolute paths does the worker log actually mention?
grep -oE '/home/[A-Za-z0-9._/-]+' "$GITREINS_JOBDIR/$JOB.log" | sort -u
# 3) Foreign checkout present? -> quarantine the verdict.
if grep -q '~/<project>' "$GITREINS_JOBDIR/$JOB.log"; then
echo "NON-VERDICT: evaluator ran in a foreign tree; do not count this INCOMPLETE"
fi
Interpretation:
job record workdir |
log paths | diagnosis |
|---|---|---|
~/bunker |
~/<project> |
worker ignored the record / wrong cwd — pinned workdir bug |
~/<project> |
~/<project> |
dispatcher resolved the wrong root (bad cwd / get_workdir fallback) |
~/bunker |
~/bunker |
genuine verdict — judge the work normally |
Caveat: some builds name the log with a fresh random id rather than the job id (a separate log‑naming bug). Always use the path printed by the dispatch banner (
log: …) ifjob-<id>.logis absent.
Record it as NON_VERDICT (wrong workdir) for INT-CI-034. It must not decrement
the task, mark it incomplete, or trigger rework. Keep the job id and log as
evidence of the runner defect.
cd ~/bunker
git rev-parse --show-toplevel # must print: ~/bunker
git rev-parse --short HEAD # must print: 4d2e775
# The criterion's own artifacts exist and are committed HERE:
test -f scripts/install.sh && echo "install.sh present"
test -f scripts/install_test.sh && echo "install_test.sh present"
git log --oneline -- scripts/install_test.sh # non-empty => committed in this repo
git show 4d2e775:scripts/install_test.sh | head
# Run the criterion's cells directly (the same dry-run cells CI runs):
bash scripts/install_test.sh
For CI‑contract criteria, the repo's own CI on the exact pushed commit is the strongest judge — it runs the criterion's cells on a clean, bare runner, with no ambient cwd.
# List runs for the commit
gh run list --repo deployBunker/bunker --commit 4d2e775 --limit 5
# Pick the run and inspect every job (expect all success, incl. unit-tests)
RUN=$(gh run list --repo deployBunker/bunker --commit 4d2e775 --limit 1 \
--json databaseId -q '.[0].databaseId')
gh run view "$RUN" --repo deployBunker/bunker \
--json jobs -q '.jobs[] | "\(.name)\t\(.status)\t\(.conclusion)"'
# Optional: confirm the criterion's cells actually executed
gh run view "$RUN" --repo deployBunker/bunker --log | grep -n 'install_test'
Substituted evidence for this instance: CI run on 4d2e775: all 5 jobs success,
including unit-tests, which executed the criterion's own dry-run cells on a bare
runner. That is the verdict of record.
Only if you need a fresh Tier‑2 verdict, never from a parent directory:
cd ~/bunker
git rev-parse --show-toplevel # ~/bunker
gitreins judge INT-CI-034 --async
# banner MUST print workdir: ~/bunker and the new job id
gitreins judge --status <new-job-id>
# then re-run the log check in section 4 against the new job
Put this wrapper on PATH as gitreinsr (or alias gitreins) so the CLI can
never be started from a parent directory:
#!/usr/bin/env bash
# gitreinsr — run gitreins pinned to the enclosing git repo root.
set -euo pipefail
root="$(git rev-parse --show-toplevel 2>/dev/null)" || {
echo "gitreinsr: not inside a git repo (cwd=$PWD)" >&2; exit 2; }
cd "$root"
exec gitreins "$@"
This fixes the dispatcher cwd. It is not sufficient if the detached worker re-resolves the repo itself — apply 6.2 as well.
Edit the async dispatch/worker in gitreins/cli.py (and mirror it in any MCP
judge.evaluate async path) so a single absolute value travels from dispatch to
evaluation and cannot silently change.
Dispatcher (_cmd_judge_async):
workdir = os.path.abspath(get_workdir()) # resolved ONCE, in the dispatcher
job_id = new_job_id()
log_path = job_log_path(job_id) # same id as the record (no stray id)
logf = open(log_path, "ab")
proc = subprocess.Popen(
[sys.executable, "-m", "gitreins.cli", "judge", "--run-job", job_id],
cwd=workdir, # <-- pin the detached child's cwd
start_new_session=True,
stdout=logf, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL,
)
job = make_job(task_id, workdir) # <-- workdir persists in the record
job["id"] = job_id
job["pid"] = proc.pid
save_job(job)
Worker (_cmd_judge_worker): trust only the record, and fail closed:
wd = os.path.abspath(job["workdir"])
# Never silently evaluate a different tree.
if not (os.path.isdir(os.path.join(wd, ".git"))
or os.path.isdir(os.path.join(wd, ".gitreins"))):
job.update(status="error",
error=f"pinned workdir is not a gitreins repo: {wd}",
finished_at=time.time())
save_job(job)
print(job["error"], file=sys.stderr)
sys.exit(1)
os.chdir(wd) # belt-and-suspenders for cwd-relative tools
tm = TaskManager(wd)
task = tm.get(job["task_id"])
...
judge = Judge(llm, wd, guard_config=load_config(wd))
Why this closes the class of bug:
workdir is resolved once, in the process the user actually ran.cwd and as data in the job record, so
the two can no longer disagree.$PWD and grading a neighbour checkout.Add a cheap post‑evaluation check before persisting, so a wrong‑tree run is reported as a runner error, not a work verdict:
import re
def foreign_evidence(wd: str, evidence_text: str) -> list[str]:
"""Absolute ~ paths in the verdict that are not under wd."""
wd = os.path.realpath(wd)
bad = []
for p in set(re.findall(r"/home/[A-Za-z0-9._/-]+", evidence_text or "")):
if not os.path.realpath(p).startswith(wd + os.sep):
bad.append(p)
return sorted(bad)
# in the worker, before job["status"] = "complete":
bad = foreign_evidence(wd, result.summary + "\n" + "\n".join(getattr(result, "items", [])))
if bad:
job.update(status="error",
error="NON-VERDICT: evidence names paths outside pinned workdir "
f"{wd}: {bad[:5]}",
finished_at=time.time())
save_job(job)
print(job["error"], file=sys.stderr)
sys.exit(1)
This turns the exact failure mode (evidence under ~/<project>) into
a loud runner error and makes the "async INCOMPLETE from a foreign checkout"
recognition rule automatic.
EXPECTED=~/bunker
JOB=job-874fc77a1801490bb24a94472cd7f40f
GITREINS_JOBDIR="${GITREINS_JOB_DIR:-$HOME/.local/share/gitreins/jobs}"
python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['workdir'])" \
"$GITREINS_JOBDIR/$JOB.json" # observe the pinned workdir
grep -n '<project>' "$GITREINS_JOBDIR/$JOB.log" # foreign paths present => confirmed
Expected: the log names ~/<project>; the INCOMPLETE is therefore a
non‑verdict for deployBunker/bunker.
cd ~/bunker
test "$(git rev-parse --show-toplevel)" = ~/bunker && echo "correct repo"
git cat-file -e 4d2e775:scripts/install_test.sh && echo "criterion file committed at 4d2e775"
gh run list --repo deployBunker/bunker --commit 4d2e775
Expected: file exists in the commit; CI on 4d2e775 is green (5/5 jobs incl.
unit-tests). This is the substituted verdict of record.
Create a two‑repo host layout, force the bad condition, and prove the new code still evaluates the right tree:
tmp="$(mktemp -d)"; cd "$tmp"
for r in right wrong; do
mkdir "$r"; git -C "$r" init -q
git -C "$r" -c user.email=t@t -c user.name=t commit -q --allow-empty -m init
done
# make the detached runner's ambient cwd the WRONG repo
cd "$tmp/wrong"
# OLD behavior: worker could grade $tmp/wrong.
# NEW behavior: dispatch pins $tmp/right.
cd "$tmp/right"
git rev-parse --show-toplevel # $tmp/right
gitreins judge INT-CI-034 --async
Then assert all of the following:
JOB=<new job id>; D="${GITREINS_JOB_DIR:-$HOME/.local/share/gitreins/jobs}"
python3 -c "import json,sys; assert json.load(open(sys.argv[1]))['workdir']=='$tmp/right'" "$D/$JOB.json"
! grep -q "$tmp/wrong" "$D/$JOB.log" # no foreign paths in the run
Expected: the job record's workdir is $tmp/right and the log contains no
$tmp/wrong paths. (For the foreign‑evidence guard in 6.3, temporarily plant a
~/wrong path in the evidence and confirm the job ends error with
NON-VERDICT, not complete.)
gitreins task complete run from a parent dir persists a verdict with no .git
and leaves tasks.yaml in_progress. Guard against it:
cd ~/bunker
test -d .git || test -d .gitreins || { echo "refusing: not the repo root"; exit 2; }
gitreins task complete INT-CI-034
git -C ~/bunker show HEAD:.gitreins/tasks.yaml | grep -A2 'INT-CI-034' # status: complete
gitreins task complete (and any state‑writing command) run from a parent
directory persists the verdict without a .git and leaves tasks.yaml
in_progress. Always run gitreins from inside the repo. The wrapper in 6.1
enforces this for every command, not just judge.
workdir.git rev-parse --show-toplevel, file present, git log for the file).# Evidence - Problem class: gitreins-async-judge-wrong-workdir - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-22T13:06:50.075Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "ASYNC TIER-2 JUDGE EVALUATES THE WRONG REPO. Symptom: gitreins judge <ID> --async dispatched from ~/bunker (dispatch banner even printed workdir: ~/bunker) returned INCOMPLETE whose item text referenced only ~/<project> paths: ls scripts/ listed <project> files, the criterion's scripts/install_test.sh did not exist there, and the verdict concluded the deliverable was never committed (git log empty for the file IN THAT REPO). Root cause: the detached background job runner does not inherit or pin the dispatching CLI's working directory; it launched its evaluator shell in a different project checkout on the same host (<project>), so every grep/log probe legitimately failed - the evaluation was of the wrong tree, not of missing work. Recognition rule: an async INCOMPLETE is a NON-VERDICT for the task's repo when its evidence paths name a foreign project checkout; the job log (jobs/job-<id>.log) preserves the evaluated paths. Fix/verification path that worked: (1) read the job log and confirm the foreign workdir; (2) do NOT count the INCOMPLETE as a judge failure of the work; (3) substitute an independent verifier that runs in the right repo - for CI-contract criteria the repo's own CI job on the pushed commit is the strongest judge (the unit-tests job on the bare runner executed exactly the criterion's cells and passed); (4) optionally re-dispatch the judge with the CLI started from the correct repo root. Related trap from the same family: gitreins task complete run from a PARENT dir persists the verdict without a .git and tasks.yaml stays in_progress - always run gitreins from INSIDE the repo.", "environment": "gitreins pipx install (real copy of gitreins-poc), bunker Go repo, detached judge job runner on shared multi-repo host", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-async-judge-wrong-workdir", "provider": "openrouter", "solved_at": "2026-09-22T13:06:50.075Z", "version": "bunker 4d2e775 / gitreins job-874fc77a"}