Probe 6 (2026-05-08) showed git push failing twice with exit 1 and
zero visible output — the previous form `if cmd 2>&1; then` redirected
git's stderr to stdout where the entrypoint's stderr-only log capture
missed it. CP-side log showed only the bash `set -x` trace, not the
actual git error message (e.g., "Permission denied (publickey)" or
"remote: pre-receive hook rejected").
Refactor the push retry loop:
- Capture output to PUSH_OUT via $() with `2>&1`
- Wrap in set +e/set -e to detect non-zero without aborting
- echo PUSH_OUT to stderr (where set -x trace also goes) so the
entrypoint's stderr capture sees it
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 2026-05-08 attempt-2 dogfood batch had 8/8 tasks "succeed" with
zero branches pushed. Root cause: my AR-21 diff-verification block was
running under set -euo pipefail without explicit error handling. A
single non-zero exit anywhere in the `git diff | tr | sed` pipeline
killed finalize.sh before the metadata write or push ran.
Specific risk: `git diff <REF_HEAD>..HEAD` returns non-zero when the
SHA is unreachable (e.g., shallow clone with init.sh fork-fallback
where upstream-ref wasn't fetched). pipefail then kills the pipeline,
set -e kills the script.
Fix: wrap the entire AR-21 block in `set +eo pipefail` (with explicit
`set -eo pipefail` restore at the end). Also:
- Use `${arr[@]:-}` instead of `${arr[@]}` for set -u safety on empty
arrays
- Add `|| true` to git command substitutions (belt-and-braces)
- Use `printf` instead of `echo` for the comma-wrap (more portable)
Verified locally: when `/workspace/reference/main/.git` is absent the
block correctly skips with the existing fallback; when present and
upstream-ref is reachable, the block runs and reports DIFF_VERIFIED.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three load-bearing fixes for the airouter dogfood pipeline, derived from
the 2026-05-08 batch-3 dogfood postmortem (gotchas-airouter.md items 27-30):
1. agent-repo/v1/init.sh — seed fresh task branches from
/workspace/reference/main/ (the upstream clone) rather than the agent
repo's stale main. This was THE killer for batch 3: the
agent-runtimes-agents fork has been frozen at 2026-05-04 since the
"Fork cleanup" PR, so every agent started from old state, missing
recent test files and the M16/M22 scripts to delete. The fork remains
the push remote (so finalize.sh works); only the working-tree seed
moves to the upstream reference. Falls back to fork main when the
reference clone isn't available (preserves legacy behavior). Tagged
AR-14a.
2. requires_labels on contexts/composites — airouter context + both
airouter composites declare requires_labels: [airouter] so the
dispatcher's _collect_supported_harnesses (with the matching agent-
runtimes change) advertises them only on dispatchers carrying the
airouter label. Stops the main dispatcher from claiming airouter-
labeled tasks and dying at init time. Composites that wrap label-
restricted contexts MUST redeclare their own requires_labels — no
auto-traversal of layers (kept simple).
3. agent-repo/v1/finalize.sh — AR-21 diff-against-upstream verification.
New env-var protocol:
- AGENT_EXPECTED_CHANGED_FILES (comma-separated paths that MUST
appear in `git diff <ref/main>..HEAD`)
- AGENT_FORBIDDEN_CHANGED_FILES (paths that MUST NOT appear)
finalize.sh fails the task (exit 1) if either invariant is violated;
the branch is still pushed for forensics so the operator can inspect.
Catches BOTH the false-success mode (item 30 — agent reports succeeded
but never changed the target file) AND the destructive-Write mode
(item 21 — task 4a2f2988 stripped 9 unrelated functions). Also writes
diff_verified, diff_mismatch, diff_changed_files into ci_metadata.json.
CRS pulls all three on next CP poll — no agent-runtimes image rebuild
needed for the framework parts. The matching dispatcher poller filter
ships in agent-runtimes (separate commit).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>