Problem class: codex-429-usage-limit-fallback-glm-dispatch
Diagnosed and verified. The solution is written to /workspace/solution.md, with the runnable wrapper at /workspace/foreman-fallback.sh.
Problem class: codex-429-usage-limit-fallback-glm-dispatch
Symptom: hermes chat worker on openai-codex/gpt-5.6-sol fails fast (~18–49 s, exit 1) with API 429 "usage limit reached"; zero work lands; foreman records a tick failure. 6th consecutive occurrence t695–t701.
Provider layer is correct. In packages/ai/src/api/openai-codex-responses.ts, an account-level codex 429 is explicitly terminal/non-retryable:
function isTerminalRateLimitError(errorText: string): boolean {
return /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(errorText);
}
function isRetryableError(status: number, errorText: string): boolean {
if (status === 429 && isTerminalRateLimitError(errorText)) return false; // no retry
...
}
The catch block also refuses retry on "usage limit". This is intentional — retrying a plan quota just burns wall-clock.
Orchestration layer is broken. The codex quota is a plan-level pool shared by the whole sol/luna class, so 429 usage limit on sol means the entire codex lane is exhausted. The condition is fast-failing, HEAD is unchanged, and no work was attempted — it is lane-unavailable, not a tick failure. The foreman equates exit 1 with failure and never re-dispatches, so every tick deterministically re-hits it.
Classify the exit at the foreman dispatch layer and re-route the same brief to GLM when it is a fast, no-work, account-quota failure.
exit 0 -> success
exit != 0 AND HEAD moved -> work_landed_but_failed (never re-dispatch)
exit != 0 AND elapsed > failfast_max -> tick_failure (real execution bug)
exit != 0 AND fast AND quota signature -> lane_unavailable -> re-dispatch on GLM
otherwise -> tick_failure
Quota signature: usage[ _-]limit | usage_limit_reached | GoUsageLimitError | FreeUsageLimitError | insufficient_quota | quota exceeded | out of budget | available balance | exceeded your current quota (default fast-fail ceiling 120 s).
Drop-in wrapper (env-overridable; full file at /workspace/foreman-fallback.sh):
#!/usr/bin/env bash
set -euo pipefail
REPO="${FOREMAN_REPO:-$PWD}"; BRIEF_FILE="${FOREMAN_BRIEF:-}"
WORKER_SESSION="${FOREMAN_SESSION:-coding-hermes-worker}"
PRIMARY_MODEL="${FOREMAN_PRIMARY_MODEL:-gpt-5.6-sol}"; PRIMARY_PROVIDER="${FOREMAN_PRIMARY_PROVIDER:-openai-codex}"
FALLBACK_MODEL="${FOREMAN_FALLBACK_MODEL:-z-ai/glm-5.3-flash}"; FALLBACK_PROVIDER="${FOREMAN_FALLBACK_PROVIDER:-openrouter-eduos}"
FAILFAST_MAX_SECONDS="${FOREMAN_FAILFAST_MAX_SECONDS:-120}"; LOG_DIR="${FOREMAN_LOG_DIR:-$REPO/.foreman-logs}"
LANE_UNAVAILABLE_RE='usage[ _-]limit|usage_limit_reached|GoUsageLimitError|FreeUsageLimitError|insufficient_quota|quota exceeded|out of budget|available balance|exceeded your current quota'
log(){ printf '[foreman-fallback] %s\n' "$*" >&2; }
head_sha(){ git -C "$REPO" rev-parse HEAD 2>/dev/null || echo ""; }
worktree_dirty(){ [ -n "$(git -C "$REPO" status --porcelain 2>/dev/null)" ]; }
classify_dispatch(){ # rc elapsed baseline current logfile
local rc="$1" elapsed="$2" baseline="$3" current="$4" logfile="$5"
[ "$rc" -eq 0 ] && { echo success; return 0; }
if [ -n "$baseline" ] && [ "$baseline" != "$current" ]; then echo work_landed_but_failed; return 0; fi
if [ "$elapsed" -gt "$FAILFAST_MAX_SECONDS" ]; then echo tick_failure; return 0; fi
if [ -f "$logfile" ] && grep -Eiq "$LANE_UNAVAILABLE_RE" "$logfile"; then echo lane_unavailable; return 0; fi
echo tick_failure
}
run_worker(){ # model provider logfile
local model="$1" provider="$2" logfile="$3"
hermes chat -m "$model" --provider "$provider" -s "$WORKER_SESSION" --ignore-rules -Q "$BRIEF_FILE" >"$logfile" 2>&1
}
verify_landing(){ # expected_trailer expected_paths(newline separated)
local expected_trailer="${1:-}" expected_paths="${2:-}" rc=0 new_head changed up
new_head="$(head_sha)"; log "landed HEAD: $new_head"; git -C "$REPO" log -1 --stat
if [ -n "$expected_paths" ]; then
changed="$(git -C "$REPO" diff --name-only HEAD~1..HEAD 2>/dev/null | sort)"
while IFS= read -r p; do [ -z "$p" ] && continue
grep -qxF "$p" <<<"$changed" || { log "SCOPE VIOLATION: unexpected path '$p' changed"; rc=1; }
done <<<"$expected_paths"
fi
if [ -n "$expected_trailer" ]; then git -C "$REPO" log -1 --format=%B | grep -qF "$expected_trailer" || { log "TRAILER MISSING: '$expected_trailer'"; rc=1; }; fi
up="$(git -C "$REPO" rev-parse --abbrev-ref '@{u}' 2>/dev/null || true)"
if [ -z "$up" ]; then log "NO UPSTREAM configured"; rc=1
elif [ "$(git -C "$REPO" rev-parse HEAD)" != "$(git -C "$REPO" rev-parse '@{u}')" ]; then log "NOT PUSHED: HEAD != $up"; rc=1; fi
return "$rc"
}
dispatch(){
[ -n "$BRIEF_FILE" ] || { echo "FOREMAN_BRIEF must point at the brief file" >&2; return 64; }
mkdir -p "$LOG_DIR"; local tick="${FOREMAN_TICK:-t000}" baseline elapsed start end rc verdict
baseline="$(head_sha)"; log "baseline HEAD: ${baseline:-<none>} (tick $tick)"
local primary_log="$LOG_DIR/${tick}.primary.log"; start="$(date +%s)"
set +e; run_worker "$PRIMARY_MODEL" "$PRIMARY_PROVIDER" "$primary_log"; rc=$?; set -e
elapsed=$(( $(date +%s) - start ))
verdict="$(classify_dispatch "$rc" "$elapsed" "$baseline" "$(head_sha)" "$primary_log")"
log "primary rc=$rc elapsed=${elapsed}s verdict=$verdict"
[ "$verdict" = lane_unavailable ] || { log "no fallback taken (verdict=$verdict)"; return "$rc"; }
if [ "$(head_sha)" != "$baseline" ] || worktree_dirty; then
log "REFUSING fallback: HEAD moved or worktree dirty after primary failure"; return 2; fi
log "codex lane unavailable (sol==luna quota). Re-dispatching brief on $FALLBACK_MODEL"
local fallback_log="$LOG_DIR/${tick}.fallback.log"
set +e; run_worker "$FALLBACK_MODEL" "$FALLBACK_PROVIDER" "$fallback_log"; rc=$?; set -e
log "fallback rc=$rc (log: $fallback_log)"; return "$rc"
}
Wire the tick handler to call foreman-fallback.sh (env FOREMAN_REPO/FOREMAN_BRIEF/FOREMAN_TICK/FOREMAN_LOG_DIR), launching the long worker with terminal background=true, then call verify_landing "<trailer>" "<expected paths>".
Immediate manual runbook for t701
BASE=$(git log -1 --format=%H)
tail -n 40 .foreman-logs/t701.primary.log | grep -Ei 'usage limit|429' # confirm signature
git log -1 --format='%H %s' # must equal $BASE
git status --porcelain # must be empty
hermes chat -m z-ai/glm-5.3-flash --provider openrouter-eduos \
-s coding-hermes-worker --ignore-rules -Q "$BRIEF_FILE" # background=true, ~20-25 min
# then foreman-verifies scope/trailer/push
--selftest): all 7 cases pass — success; three quota signatures → lane_unavailable; HEAD-advanced → work_landed_but_failed; non-quota fast error → tick_failure; slow quota error → tick_failure.hermes: primary gpt-5.6-sol returned the real 429 text (exit 1) → verdict=lane_unavailable → fallback z-ai/glm-5.3-flash landed the commit with trailer Foreman-Tick: t701, fallback rc=0.work_landed_but_failed, no double dispatch); verify_landing flagged a scope violation (rc=1) and passed clean scope + trailer + pushed upstream (rc=0).solution.md passes bash -n and its selftest.Production acceptance: a codex-quota tick must log verdict=lane_unavailable → Re-dispatching brief → fallback rc=0, and verify_landing must return 0 with expected scope, trailer, and in-sync upstream. No tick may report tick_failure for a fast codex 429 usage limit.
# Evidence - Problem class: codex-429-usage-limit-fallback-glm-dispatch - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-16T21:57:12.360Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "EduOS foreman exec tick: hermes chat worker dispatch on openai-codex gpt-5.6-sol fails fast (~18-49s, exit 1) with API 429 usage limit reached - sub-wide sol==luna class, zero work landed. Validated pattern: treat as lane-unavailable not tick failure; verify HEAD unchanged (git log -1) before re-dispatch; re-run the SAME brief file with -m z-ai/glm-5.3-flash --provider openrouter-eduos -s coding-hermes-worker --ignore-rules -Q via terminal background=true; worker completes ~20-25 min; foreman verifies commit scope/trailer/push itself. 6th consecutive occurrence t695-t701.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "codex-429-usage-limit-fallback-glm-dispatch", "provider": "openrouter", "solved_at": "2026-09-16T21:57:12.360Z", "version": ""}