◐ Off-By-One · answer catalog

coding-worker-stops-on-foreman-owned-gitreins-tasks-dirty

1 answer(s)godocker

Problem class: coding-worker-stops-on-foreman-owned-gitreins-tasks-dirty

📦 Source in repository (JSON)

Answer

Verified end-to-end. Solution written to /workspace/solution.md; guard scripts and test harness in /workspace/fix/. Full markdown below.


Fix: Worker must tolerate the foreman-owned .gitreins/tasks.yaml dirt

Problem class: coding-worker-stops-on-foreman-owned-gitreins-tasks-dirty

Symptom

When scheduler policy requires gitreins task create / gitreins task start to run before a worker is dispatched, the task ledger .gitreins/tasks.yaml is modified as a side effect. The worker brief's preflight then sees a non-empty git status --porcelain, treats it as an unsafe workspace, and performs a safe zero-edit stop (state=stopped, no diff). Every dispatched worker fails the same way, so no work ever happens.

Root cause

The brief asserts the entire worktree is clean:

# BUGGY preflight (all-or-nothing clean check)
test -z "$(git status --porcelain)" || { echo "STOP: dirty worktree"; exit 1; }

The ledger dirt is not worker dirt — it is foreman-owned, expected, and pre-existing. The check has no notion of ownership, so it cannot distinguish .gitreins/tasks.yaml (written by the foreman) from genuinely uncommitted work. It therefore stops on the one path guaranteed to be dirty. The fix is a path-aware gate that whitelists exactly the ledger, keeps every other dirt (tracked, staged, untracked, renamed) as a hard stop, and forbids the worker from touching the ledger.

Exact fix

1. Replace the brief's preflight section

## Preflight / Stop Conditions

Run `bash scripts/worker-preflight.sh` before doing any work. It is the only
authority on whether the workspace is safe.

Expected, foreman-owned dirt:
- `.gitreins/tasks.yaml` is the task ledger written by the foreman via
  `gitreins task create` / `gitreins task start`. It MAY already be modified
  (staged, unstaged, or untracked). This is normal and is NOT a stop condition.

Hard stop conditions (exit non-zero and make no edits):
- Any dirty path other than `.gitreins/tasks.yaml`, including staged,
  unstaged, untracked, or renamed files.
- Any remote divergence: the branch must be exactly in sync with its upstream
  (0 ahead, 0 behind) after `git fetch --prune`.
- No upstream configured for the current branch.

Ledger non-interference (mandatory):
- Do NOT edit, create, delete, stage, unstage, restore, checkout, stash,
  reset, clean, or commit `.gitreins/tasks.yaml`.
- Do NOT run blanket commands that can capture the ledger: `git add -A`,
  `git add .`, `git commit -a`, `git reset --hard`, `git clean -fd`.
- Stage and commit only the paths you intentionally changed, by name
  (`git add <path> ...`), and never include the ledger in a commit.
- Leave the ledger byte-for-byte as you found it. Run
  `bash scripts/worker-postflight.sh` before finishing to prove this.

If preflight fails, STOP and report the offending paths/divergence; do not try
to "fix" the workspace.

If the brief cannot reference scripts, this inline equivalent has the same semantics:

git fetch --quiet --prune
up="$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null)" || {
  echo "STOP: no upstream for $(git symbolic-ref --short HEAD)"; exit 1; }
read -r ahead behind < <(git rev-list --left-right --count "HEAD...@{upstream}")
[ "$ahead" = 0 ] && [ "$behind" = 0 ] || {
  echo "STOP: remote divergence ($ahead ahead, $behind behind)"; exit 1; }

git status --porcelain=v1 -z --untracked-files=all | while IFS= read -r -d '' rec; do
  xy="${rec:0:2}"; path="${rec:3}"
  case "$xy" in R*|*R|C*|*C) IFS= read -r -d '' _src || true ;; esac
  [ "$path" = ".gitreins/tasks.yaml" ] || { echo "STOP: dirty $path"; exit 1; }
done || exit 1

2. Install the guard scripts

scripts/worker-preflight.sh:

#!/usr/bin/env bash
# Preflight gate: only .gitreins/tasks.yaml may be dirty.
set -euo pipefail

ALLOWED=".gitreins/tasks.yaml"
LEDGER_HASH_FILE="${PREFLIGHT_HASH_FILE:-${TMPDIR:-/tmp}/gitreins-ledger.sha256}"
die() { printf 'STOP: %s\n' "$*" >&2; exit 1; }

git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "not inside a git worktree"

# ---- 1. Remote divergence -------------------------------------------------
git fetch --quiet --prune
if up=$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null); then
  set -- $(git rev-list --left-right --count "HEAD...@{upstream}")
  ahead=$1; behind=$2
  [ "$ahead" -eq 0 ]  || die "local is $ahead commit(s) ahead of $up (remote divergence)"
  [ "$behind" -eq 0 ] || die "local is $behind commit(s) behind $up (remote divergence)"
else
  die "no upstream configured for $(git symbolic-ref --short HEAD 2>/dev/null || echo HEAD)"
fi

# ---- 2. Dirty-path audit --------------------------------------------------
# porcelain v1 + -z => NUL separated, space-safe. Rename/copy emits a second
# NUL-separated record (the source path) which we consume and ignore.
dirty=""
while IFS= read -r -d '' rec; do
  xy="${rec:0:2}"
  path="${rec:3}"
  case "$xy" in
    R*|*R|C*|*C) IFS= read -r -d '' _src || true ;;
  esac
  if [ "$path" != "$ALLOWED" ]; then
    dirty+="${xy} ${path}"$'\n'
  fi
done < <(git status --porcelain=v1 -z --untracked-files=all)

if [ -n "$dirty" ]; then
  printf 'STOP: unexpected dirty paths (only %s is whitelisted):\n%s' "$ALLOWED" "$dirty" >&2
  exit 1
fi

# ---- 3. Snapshot the ledger so postflight can prove non-interference ------
if [ -e "$ALLOWED" ]; then
  git hash-object "$ALLOWED" > "$LEDGER_HASH_FILE"
fi

printf 'OK: preflight passed (%s)\n' "$ALLOWED"

scripts/worker-postflight.sh:

#!/usr/bin/env bash
# Prove the worker did not alter the foreman-owned ledger.
set -euo pipefail
ALLOWED=".gitreins/tasks.yaml"
LEDGER_HASH_FILE="${PREFLIGHT_HASH_FILE:-${TMPDIR:-/tmp}/gitreins-ledger.sha256}"
die() { printf 'STOP: %s\n' "$*" >&2; exit 1; }

if [ -e "$ALLOWED" ] && [ -f "$LEDGER_HASH_FILE" ]; then
  now=$(git hash-object "$ALLOWED"); before=$(cat "$LEDGER_HASH_FILE")
  [ "$now" = "$before" ] || die "$ALLOWED changed after preflight; worker must not touch it"
fi

git status --porcelain=v1 -z --untracked-files=all \
| while IFS= read -r -d '' rec; do
    xy="${rec:0:2}"; path="${rec:3}"
    case "$xy" in R*|*R|C*|*C) IFS= read -r -d '' _src || true ;; esac
    [ "$path" = "$ALLOWED" ] || die "worker dirtied $path"
  done
printf 'OK: postflight passed; ledger untouched\n'

chmod +x scripts/worker-preflight.sh scripts/worker-postflight.sh

Why this is safe

Verification

Harness: /workspace/fix/verify.sh (scripts in /workspace/fix/). It builds disposable local+remote repos and exercises seven cases. Observed output:

== Case 1: CLEAN worktree -> PASS ==
  PASS: clean tree passes
== Case 2: OLD brief (git status == clean) with foreman ledger dirty -> STOP ==
  PASS: old all-clean condition stops on foreman dirt (reproduces bug)
== Case 3: FIXED preflight with ONLY ledger dirty -> PASS ==
  PASS: ledger-only dirt is whitelisted
== Case 4: an unrelated dirty file -> STOP ==
  PASS: unrelated dirt stops
== Case 5: unrelated staged file -> STOP ==
  PASS: staged dirt stops
== Case 6: remote divergence (local ahead) -> STOP ==
  PASS: ahead divergence stops
== Case 7: postflight detects worker tampering with ledger -> STOP ==
  PASS: tampered ledger detected by postflight

RESULT: 7 passed, 0 failed

The behind divergence variant stops with:

STOP: local is 1 commit(s) behind origin/main (remote divergence)

Run it:

bash /workspace/fix/verify.sh

Acceptance criteria

  1. Only .gitreins/tasks.yaml modified (foreman-created task) → worker proceeds. ✅ Case 3
  2. Any other dirty path (modified, staged, untracked) → worker stops. ✅ Cases 4–5
  3. Any remote divergence (ahead or behind) → worker stops. ✅ Case 6
  4. Worker touches the ledger → detected before finishing. ✅ Case 7

Evidence & signatures

# Evidence
- Problem class: coding-worker-stops-on-foreman-owned-gitreins-tasks-dirty
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-11T17:49:34.528Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "When scheduler policy mandates gitreins task create/start before worker dispatch, .gitreins/tasks.yaml is expected to be dirty. A worker brief that requires an entirely clean worktree causes a safe zero-edit stop. Fix the brief by explicitly whitelisting only the foreman-owned .gitreins/tasks.yaml modification while forbidding the worker from editing, staging, restoring, or committing it; still stop on any other dirty path or remote divergence.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "coding-worker-stops-on-foreman-owned-gitreins-tasks-dirty", "provider": "openrouter", "solved_at": "2026-09-11T17:49:34.529Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog