#!/usr/bin/env bash
# subfleet claude — hardened headless Claude Code dispatch pinned to an enrolled
# account lane. Auto mode selects a lane and immediately re-picks after a hard
# account limit; pinned mode reports that condition with rc=4. Authentication
# failures are rc=5, while unrelated raw provider exit codes 4/5 are normalized.
#
# Dirty work is snapshotted under refs/claude-salvage/* without moving HEAD or
# the real index. Completed transcripts are checked for silent model changes,
# and every run is offered to subfleet's best-effort usage and durable ledgers.
#
# Usage:
#   subfleet claude -a <email> | -A -C <workdir> -p <prompt_file> -o <out_file> \
#               [-m <model>] [-s workspace-write|read-only] \
#               [-b <salvage_branch>] [-R <salvage_remote>] \
#               [-r <max_retries>] [-d]
#
# A salvage branch push fires on every exit, including success. For a public
# remote, omit -b or use -R with a private recovery remote until work is reviewed.
set -u

# Follow repository-local symlinks so detached re-exec and sibling command
# discovery stay attached to this checkout.
SCRIPT_SOURCE=${BASH_SOURCE[0]}
while [ -h "$SCRIPT_SOURCE" ]; do
  SCRIPT_DIR=$(cd -P "$(dirname "$SCRIPT_SOURCE")" >/dev/null 2>&1 && pwd)
  SCRIPT_SOURCE=$(readlink "$SCRIPT_SOURCE")
  case "$SCRIPT_SOURCE" in
    /*) ;;
    *) SCRIPT_SOURCE="$SCRIPT_DIR/$SCRIPT_SOURCE" ;;
  esac
done
SCRIPT_DIR=$(cd -P "$(dirname "$SCRIPT_SOURCE")" >/dev/null 2>&1 && pwd)
SCRIPT_SOURCE="$SCRIPT_DIR/$(basename "$SCRIPT_SOURCE")"

ORIG_ARGS=("$@")
EMAIL="" AUTO=0 MODEL="claude-fable-5" WORKDIR="" PROMPT_FILE="" OUT_FILE=""
SANDBOX="workspace-write" SALVAGE_BRANCH="" SALVAGE_REMOTE="origin" MAX_RETRIES=2 DETACH=0
while getopts "a:Am:C:p:o:s:b:R:r:d" opt; do
  case "$opt" in
    a) EMAIL=$OPTARG ;;
    A) AUTO=1 ;;
    m) MODEL=$OPTARG ;;
    C) WORKDIR=$OPTARG ;;
    p) PROMPT_FILE=$OPTARG ;;
    o) OUT_FILE=$OPTARG ;;
    s) SANDBOX=$OPTARG ;;
    b) SALVAGE_BRANCH=$OPTARG ;;
    R) SALVAGE_REMOTE=$OPTARG ;;
    r) MAX_RETRIES=$OPTARG ;;
    d) DETACH=1 ;;
    *) exit 2 ;;
  esac
done

DETACH_TMP_ROOT=${CLAUDE_LANE_TMPDIR:-${TMPDIR:-/tmp}}
DETACH_TMP_ROOT=${DETACH_TMP_ROOT%/}
DETACHED_CHILD=0

owned_prompt_shape() {
  local candidate=$1
  [ -n "$candidate" ] || return 1
  [ "$(dirname -- "$candidate")" = "$DETACH_TMP_ROOT" ] || return 1
  case "$(basename -- "$candidate")" in
    subfleet-claude-prompt.*) return 0 ;;
    *) return 1 ;;
  esac
}

cleanup_owned_prompt() {
  local owned_prompt=${CLAUDE_LANE_OWNED_PROMPT:-}
  [ "$DETACHED_CHILD" = 1 ] || return 0
  if ! owned_prompt_shape "$owned_prompt"; then
    echo "subfleet claude: WARNING refusing unexpected owned prompt path $owned_prompt" >&2
    return 1
  fi
  if ! rm -f "$owned_prompt" || [ -e "$owned_prompt" ]; then
    echo "subfleet claude: WARNING could not remove owned prompt $owned_prompt" >&2
    return 1
  fi
  unset CLAUDE_LANE_OWNED_PROMPT CLAUDE_LANE_DETACHED_PROMPT
}

# A detached child owns only the exact regular prompt copy named by the parent.
# Installing this narrow trap before validation prevents leaks on early errors.
if [ "$DETACH" = 1 ] \
   && [ "${CLAUDE_LANE_DETACHED:-}" = 1 ] \
   && [ -n "${CLAUDE_LANE_OWNED_PROMPT:-}" ] \
   && [ "$PROMPT_FILE" = "$CLAUDE_LANE_OWNED_PROMPT" ] \
   && [ -f "$CLAUDE_LANE_OWNED_PROMPT" ] \
   && owned_prompt_shape "$CLAUDE_LANE_OWNED_PROMPT"; then
  DETACHED_CHILD=1
  trap cleanup_owned_prompt EXIT
fi

[ -n "$EMAIL" ] || [ "$AUTO" = 1 ] || { echo "subfleet claude: need -a <email> or -A" >&2; exit 2; }
[ -z "$EMAIL" ] || [ "$AUTO" = 0 ] || { echo "subfleet claude: -a and -A are mutually exclusive" >&2; exit 2; }
[ -n "$WORKDIR" ] || { echo "subfleet claude: missing -C <workdir>" >&2; exit 2; }
[ -n "$PROMPT_FILE" ] || { echo "subfleet claude: missing -p <prompt_file>" >&2; exit 2; }
[ -n "$OUT_FILE" ] || { echo "subfleet claude: missing -o <out_file>" >&2; exit 2; }
ORIGINAL_OUT_FILE=${SUBFLEET_RUN_ORIGINAL_OUT-$OUT_FILE}
[ -d "$WORKDIR" ] || { echo "subfleet claude: workdir not found: $WORKDIR" >&2; exit 2; }
[ -f "$PROMPT_FILE" ] || { echo "subfleet claude: prompt file not found: $PROMPT_FILE" >&2; exit 2; }
case "$SANDBOX" in
  workspace-write|read-only) ;;
  *) echo "subfleet claude: -s must be workspace-write or read-only" >&2; exit 2 ;;
esac
command -v jq >/dev/null || { echo "subfleet claude: jq required" >&2; exit 2; }
command -v uuidgen >/dev/null || { echo "subfleet claude: uuidgen required" >&2; exit 2; }

CLAUDE_BIN=${CLAUDE_LANE_CLAUDE:-claude}
SUBFLEET_BIN=${CLAUDE_LANE_SUBFLEET:-$SCRIPT_DIR/subfleet}
if [ ! -x "$SUBFLEET_BIN" ]; then
  SUBFLEET_BIN=$(command -v "$SUBFLEET_BIN" 2>/dev/null || command -v subfleet || true)
fi
CLAUDE_PICK=${CLAUDE_LANE_PICK:-}
CLAUDE_MODEL_BIN=""
if [ -n "${CLAUDE_LANE_CLAUDE_MODEL:-}" ]; then
  CLAUDE_MODEL_BIN=$(command -v "$CLAUDE_LANE_CLAUDE_MODEL" 2>/dev/null || true)
fi
CLAUDE_DIR=${CLAUDE_LANE_CLAUDE_DIR:-${SUBFLEET_CLAUDE_DIR:-$HOME/.claude}}
BACKOFF_BASE=${CLAUDE_LANE_BACKOFF:-60}
EXCLUDED_LANES=()
EXCLUDED_COUNT=0
RUN_ID=""
SID=""

absolute_path() {
  local dir base
  dir=$(dirname "$1")
  base=$(basename "$1")
  dir=$(cd -P "$dir" >/dev/null 2>&1 && pwd) || return 1
  printf '%s/%s\n' "$dir" "$base"
}

RESOLVED=$(cd -P "$WORKDIR" >/dev/null 2>&1 && pwd) \
  || { echo "subfleet claude: cannot resolve workdir: $WORKDIR" >&2; exit 2; }
WORKDIR=$RESOLVED
RESOLVED=$(absolute_path "$PROMPT_FILE") \
  || { echo "subfleet claude: cannot resolve prompt path: $PROMPT_FILE" >&2; exit 2; }
PROMPT_FILE=$RESOLVED
RESOLVED=$(absolute_path "$OUT_FILE") \
  || { echo "subfleet claude: output directory not found: $(dirname "$OUT_FILE")" >&2; exit 2; }
OUT_FILE=$RESOLVED

BASE="${OUT_FILE%.md}"
ERR_FILE="$BASE.err.log"
RAW_FILE="$BASE.result.json"
LANE_LOG="$BASE.lane.log"
DOWNGRADED_FILE="$BASE.DOWNGRADED"

same_file() {
  [ "$1" = "$2" ] || { [ -e "$1" ] && [ -e "$2" ] && [ "$1" -ef "$2" ]; }
}
ARTIFACTS=("$PROMPT_FILE" "$OUT_FILE" "$ERR_FILE" "$RAW_FILE" "$LANE_LOG" "$DOWNGRADED_FILE")
for ((left = 0; left < ${#ARTIFACTS[@]}; left++)); do
  for ((right = left + 1; right < ${#ARTIFACTS[@]}; right++)); do
    if same_file "${ARTIFACTS[$left]}" "${ARTIFACTS[$right]}"; then
      echo "subfleet claude: prompt/output artifacts must not alias: ${ARTIFACTS[$left]} = ${ARTIFACTS[$right]}" >&2
      exit 2
    fi
  done
done

if [ "$DETACH" = 1 ] && [ "$DETACHED_CHILD" = 0 ]; then
  DETACHED_PROMPT=$(mktemp "$DETACH_TMP_ROOT/subfleet-claude-prompt.XXXXXX" 2>/dev/null) || {
    echo "subfleet claude: could not create a durable prompt copy for detached run" >&2
    exit 2
  }
  if ! cp "$PROMPT_FILE" "$DETACHED_PROMPT"; then
    rm -f "$DETACHED_PROMPT"
    echo "subfleet claude: could not copy prompt for detached run: $PROMPT_FILE" >&2
    exit 2
  fi
  export CLAUDE_LANE_DETACHED=1
  export CLAUDE_LANE_OWNED_PROMPT="$DETACHED_PROMPT"
  # Kept for compatibility with callers that observe the detached handoff.
  export CLAUDE_LANE_DETACHED_PROMPT="$DETACHED_PROMPT"
  CHILD_ARGS=()
  PROMPT_INSERTED=0
  for arg in "${ORIG_ARGS[@]}"; do
    if [ "$arg" = -- ] && [ "$PROMPT_INSERTED" = 0 ]; then
      CHILD_ARGS+=(-p "$DETACHED_PROMPT")
      PROMPT_INSERTED=1
    fi
    CHILD_ARGS+=("$arg")
  done
  # Also leave the override at the tail for launchers that inspect the handoff;
  # the copy inserted before `--` is the one getopts consumes in that case.
  CHILD_ARGS+=(-p "$DETACHED_PROMPT")
  nohup "$SCRIPT_SOURCE" "${CHILD_ARGS[@]}" \
    </dev/null >>"$LANE_LOG" 2>&1 &
  pid=$!
  disown "$pid" 2>/dev/null || true
  echo "subfleet claude: detached pid=$pid · log=$LANE_LOG · out=$OUT_FILE"
  exit 0
fi

salvage() {
  git -C "$WORKDIR" rev-parse --git-dir >/dev/null 2>&1 || return 0
  local salvage_sha=""
  if [ -n "$(git -C "$WORKDIR" status --porcelain 2>/dev/null)" ]; then
    local gitdir tmpidx tree parent branch ts ref
    gitdir=$(git -C "$WORKDIR" rev-parse --absolute-git-dir 2>/dev/null)
    parent=$(git -C "$WORKDIR" rev-parse --verify HEAD 2>/dev/null)
    if [ -n "$gitdir" ] && [ -n "$parent" ]; then
      tmpidx="$gitdir/claude-salvage-index.$$"
      if GIT_INDEX_FILE="$tmpidx" git -C "$WORKDIR" read-tree "$parent" 2>/dev/null \
         && GIT_INDEX_FILE="$tmpidx" git -C "$WORKDIR" add -A 2>/dev/null; then
        tree=$(GIT_INDEX_FILE="$tmpidx" git -C "$WORKDIR" write-tree 2>/dev/null)
        if [ -n "$tree" ]; then
          salvage_sha=$(git -C "$WORKDIR" commit-tree "$tree" -p "$parent" \
            -m "WIP salvage: subfleet claude auto-commit (lane=${EMAIL:-unpicked} model=$MODEL)" 2>/dev/null)
        fi
      fi
      rm -f "$tmpidx"
      if [ -n "$salvage_sha" ]; then
        branch=$(git -C "$WORKDIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo detached)
        ts=$(date +%Y%m%d-%H%M%S)
        ref="refs/claude-salvage/${branch//\//-}-$ts-$$"
        git -C "$WORKDIR" update-ref "$ref" "$salvage_sha" 2>/dev/null \
          && echo "subfleet claude: salvaged dirty state → $ref ($(git -C "$WORKDIR" rev-parse --short "$salvage_sha")); branch/worktree untouched" >&2
      else
        echo "subfleet claude: WARNING dirty state present but salvage snapshot failed (unborn HEAD?) — worktree left as-is" >&2
      fi
    fi
  fi
  if [ -n "$SALVAGE_BRANCH" ]; then
    local push_src="${salvage_sha:-HEAD}"
    git -C "$WORKDIR" push -qf "$SALVAGE_REMOTE" "$push_src:refs/heads/$SALVAGE_BRANCH" 2>/dev/null \
      && echo "subfleet claude: pushed $push_src → $SALVAGE_REMOTE/$SALVAGE_BRANCH" >&2
  fi
}

record_run_start() {
  local record_args
  [ -n "$SUBFLEET_BIN" ] || return 0
  record_args=(
    _record-run --phase start --family claude --model "$MODEL"
    --lane "$EMAIL" --workdir "$WORKDIR" --prompt "$PROMPT_FILE"
    --out "$OUT_FILE" --err "$ERR_FILE" --original-out "$ORIGINAL_OUT_FILE"
  )
  [ "$DETACHED_CHILD" = 0 ] || record_args+=(--lane-log "$LANE_LOG")
  RUN_ID=$("$SUBFLEET_BIN" "${record_args[@]}" 2>/dev/null) || RUN_ID=""
}

record_run_lane() {
  [ -n "$RUN_ID" ] || return 0
  "$SUBFLEET_BIN" _record-run --phase update --run-id "$RUN_ID" \
    --lane "$EMAIL" >/dev/null 2>&1 || true
}

record_run_finish() {
  local final_rc=$1 transcript=""
  local record_args
  [ -n "$RUN_ID" ] || return 0
  record_args=(
    _record-run --phase finish --run-id "$RUN_ID" --lane "$EMAIL" --rc "$final_rc"
  )
  if [ -n "$SID" ]; then
    record_args+=(--session-id "$SID")
    transcript=$(transcript_path)
    [ ! -f "$transcript" ] || record_args+=(--transcript-path "$transcript")
  fi
  "$SUBFLEET_BIN" "${record_args[@]}" >/dev/null 2>&1 || true
}

on_exit() {
  local final_rc=$?
  trap - EXIT
  # Never let the private detached prompt enter a salvage ref or remote push.
  if ! cleanup_owned_prompt; then
    echo "subfleet claude: WARNING skipping salvage and push to protect the detached prompt" >&2
  else
    salvage
  fi
  record_run_finish "$final_rc"
  exit "$final_rc"
}
trap on_exit EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

if [ "$DETACHED_CHILD" = 1 ] && [ -n "${CLAUDE_LANE_DETACHED_START_DELAY:-}" ]; then
  sleep "$CLAUDE_LANE_DETACHED_START_DELAY"
fi

lane_is_excluded() {
  local candidate=$1 excluded
  [ "$EXCLUDED_COUNT" -gt 0 ] || return 1
  for excluded in "${EXCLUDED_LANES[@]}"; do
    [ "$candidate" = "$excluded" ] && return 0
  done
  return 1
}

exclude_lane() {
  if ! lane_is_excluded "$1"; then
    EXCLUDED_LANES[EXCLUDED_COUNT]=$1
    EXCLUDED_COUNT=$((EXCLUDED_COUNT + 1))
  fi
}

pick_lane() {
  local picked excluded tries=0
  local pick_args
  [ -n "$SUBFLEET_BIN" ] || [ -n "$CLAUDE_PICK" ] || return 1
  while [ "$tries" -lt 32 ]; do
    tries=$((tries + 1))
    if [ -n "$CLAUDE_PICK" ]; then
      # Compatibility pickers may not accept exclusions. Re-invoke a stateful
      # picker until it yields a candidate this run has not rejected.
      picked=$("$CLAUDE_PICK") || return 1
    else
      pick_args=(pick claude)
      if [ "$EXCLUDED_COUNT" -gt 0 ]; then
        for excluded in "${EXCLUDED_LANES[@]}"; do
          pick_args+=(--exclude "$excluded")
        done
      fi
      picked=$("$SUBFLEET_BIN" "${pick_args[@]}") || return 1
    fi
    [ -n "$picked" ] || return 1
    if ! lane_is_excluded "$picked"; then
      EMAIL=$picked
      record_run_lane
      return 0
    fi
  done
  return 1
}

fetch_token() {
  [ -n "$SUBFLEET_BIN" ] || {
    echo "subfleet claude: subfleet command not found" >&2
    return 1
  }
  TOKEN=$("$SUBFLEET_BIN" secret get-for-account "$EMAIL" 2>/dev/null)
  if [ -z "$TOKEN" ]; then
    record_auth_failure
    echo "subfleet claude: no stored setup token for $EMAIL" >&2
    echo "subfleet claude: run claude setup-token while signed into $EMAIL" >&2
    echo "subfleet claude: then run subfleet enroll $EMAIL" >&2
    return 1
  fi
}

pick_lane_with_token() {
  local tries=0
  while [ "$tries" -lt 32 ]; do
    tries=$((tries + 1))
    pick_lane || return 1
    fetch_token && return 0
    exclude_lane "$EMAIL"
  done
  return 1
}

transcript_path() {
  local project_key
  project_key=$( (cd "$WORKDIR" && printf '%s' "$PWD" | tr '/.' '--') )
  printf '%s\n' "$CLAUDE_DIR/projects/$project_key/$SID.jsonl"
}

record_lane_usage() {
  local transcript
  [ -n "$SUBFLEET_BIN" ] || return 0
  transcript=$(transcript_path)
  "$SUBFLEET_BIN" lane-usage record --email "$EMAIL" --session-id "$SID" \
    --transcript "$transcript" >/dev/null 2>&1 || true
}

record_auth_failure() {
  [ -n "$SUBFLEET_BIN" ] || return 0
  "$SUBFLEET_BIN" lane-usage auth-failure --email "$EMAIL" >/dev/null 2>&1 || true
}

record_hard_limit() {
  local reset
  local args=(lane-usage hard-limit --email "$EMAIL" --session-id "$SID" \
    --error-file "$ERR_FILE")
  [ -n "$SUBFLEET_BIN" ] || return 0
  [ -s "$RAW_FILE" ] && args+=(--error-file "$RAW_FILE")
  reset=$("$SUBFLEET_BIN" "${args[@]}" 2>/dev/null || true)
  if [ -n "$reset" ]; then
    echo "subfleet claude: hard limit reset=$reset" >&2
  fi
}

inline_model_report() {
  local transcript models latest refusals
  transcript=$(transcript_path)
  [ -f "$transcript" ] || {
    echo "no transcript at $transcript" >&2
    return 2
  }
  models=$(jq -r 'select(.type=="assistant") | .message.model' "$transcript" 2>/dev/null \
    | grep -v '^null$' | tail -10)
  [ -n "$models" ] || {
    echo "no assistant turns in $transcript" >&2
    return 2
  }
  latest=$(printf '%s\n' "$models" | tail -1)
  refusals=$(grep -c '"stop_reason":"refusal"' "$transcript" 2>/dev/null || true)
  echo "transcript: $transcript"
  echo "last 10 assistant turns:"
  printf '%s\n' "$models" | sort | uniq -c | sort -rn | awk '{printf "  %s x%s\n", $2, $1}'
  echo "refusal fallbacks in session: ${refusals:-0}"
  echo "latest: $latest"
}

# The transcript field is the ground truth for the model that served the lane.
check_model() {
  local report latest
  if [ -n "${CLAUDE_LANE_CLAUDE_MODEL:-}" ] && [ -z "$CLAUDE_MODEL_BIN" ]; then
    echo "subfleet claude: configured model checker not found — skipping served-model check" >&2
    return 0
  fi
  if [ -n "$CLAUDE_MODEL_BIN" ]; then
    report=$( (cd "$WORKDIR" && "$CLAUDE_MODEL_BIN" -n 10 "$SID") 2>&1 )
  else
    report=$(inline_model_report 2>&1)
  fi
  latest=$(printf '%s\n' "$report" | sed -n 's/^latest: //p' | tail -1)
  if [ -z "$latest" ]; then
    echo "subfleet claude: served-model check inconclusive (no transcript for session $SID?)" >&2
    return 0
  fi
  case "$latest" in
    "$MODEL"|*"$MODEL"*)
      echo "subfleet claude: served by $latest (as requested)" >&2 ;;
    *)
      {
        echo "subfleet claude: MODEL-DOWNGRADE — lane served $latest, requested $MODEL (classifier fallback)."
        echo "subfleet claude: treat judgment or writing output as unverified and re-check it before use."
        printf '%s\n' "$report"
      } | tee "$DOWNGRADED_FILE" >&2 ;;
  esac
}

record_run_start
unset SUBFLEET_RUN_DECISION_JSON SUBFLEET_RUN_ORIGINAL_OUT SUBFLEET_RUN_LANE_LOG

if [ "$AUTO" = 1 ] && [ -z "$EMAIL" ]; then
  pick_lane_with_token || {
    echo "subfleet claude: no dispatchable lane with a usable stored token" >&2
    exit 1
  }
else
  fetch_token || exit 5
fi

# The marker describes the current output only. A new attempt invalidates both
# an old output and its downgrade warning.
rm -f "$DOWNGRADED_FILE"

attempt=0
while :; do
  attempt=$((attempt + 1))
  SID=$(uuidgen | tr '[:upper:]' '[:lower:]')
  : > "$OUT_FILE"
  : > "$RAW_FILE"
  PERM_ARGS=()
  [ "$SANDBOX" = "workspace-write" ] && PERM_ARGS=(--dangerously-skip-permissions)
  (
    cd "$WORKDIR" || exit 97
    # Pin subscription OAuth and prevent accidental API-meter billing.
    unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN
    CLAUDE_CODE_OAUTH_TOKEN="$TOKEN" exec env \
      -u CLAUDE_LANE_DETACHED -u CLAUDE_LANE_OWNED_PROMPT \
      -u CLAUDE_LANE_DETACHED_PROMPT \
      "$CLAUDE_BIN" -p --model "$MODEL" \
      --session-id "$SID" --output-format json ${PERM_ARGS[@]+"${PERM_ARGS[@]}"}
  ) <"$PROMPT_FILE" >"$RAW_FILE" 2>"$ERR_FILE"
  rc=$?
  record_lane_usage

  if [ $rc -eq 0 ] && [ -s "$RAW_FILE" ] && [ "$(jq -r \
      'if type=="object" and .is_error==false and (.result|type=="string" and length>0) then "ok" else "bad" end' \
      "$RAW_FILE" 2>/dev/null)" = "ok" ]; then
    jq -r '.result' "$RAW_FILE" > "$OUT_FILE"
    if [ -s "$OUT_FILE" ]; then
      echo "subfleet claude: OK lane=$EMAIL model=$MODEL attempt=$attempt bytes=$(wc -c <"$OUT_FILE" | tr -d ' ') session=$SID"
      check_model
      exit 0
    fi
  fi

  ERRTXT=("$ERR_FILE")
  [ -s "$RAW_FILE" ] && ERRTXT=("$ERR_FILE" "$RAW_FILE")
  if grep -qiE "401|unauthoriz|authentication_error|oauth token|token.*(revoked|expired)|invalid.*api.?key" \
      ${ERRTXT[@]+"${ERRTXT[@]}"}; then
    record_auth_failure
    echo "subfleet claude: AUTH FAILURE on lane $EMAIL — the setup token is unusable; retrying cannot help." >&2
    echo "subfleet claude: run claude setup-token while signed into $EMAIL" >&2
    echo "subfleet claude: then run subfleet enroll $EMAIL" >&2
    exit 5
  fi
  if grep -qiE "usage limit|session limit|weekly limit|5-hour limit|subscription limit|out of extra usage" \
      ${ERRTXT[@]+"${ERRTXT[@]}"}; then
    record_hard_limit
    if [ "$AUTO" = 1 ] && [ "$attempt" -le "$MAX_RETRIES" ]; then
      OLD_EMAIL=$EMAIL
      exclude_lane "$OLD_EMAIL"
      echo "subfleet claude: lane $OLD_EMAIL hit a hard limit; re-picking (attempt $attempt/$MAX_RETRIES)" >&2
      if pick_lane_with_token; then
        continue
      fi
      echo "subfleet claude: no alternate lane available" >&2
    fi
    echo "subfleet claude: LANE LIMITED (rc=$rc) — re-dispatch with subfleet claude -A or wait for the reset (subfleet pick claude --json --all)." >&2
    exit 4
  fi
  if [ "$attempt" -le "$MAX_RETRIES" ] && grep -qiE \
      "rate limit|too many requests|429|overloaded|capacity|disconnect|connection|ECONN|ETIMEDOUT|timed? ?out|temporarily|internal server error|50[023]|529" \
      ${ERRTXT[@]+"${ERRTXT[@]}"}; then
    wait_s=$((BACKOFF_BASE * attempt))
    echo "subfleet claude: transient failure (rc=$rc), retry $attempt/$MAX_RETRIES in ${wait_s}s: $(grep -m1 -ioE 'rate limit|too many requests|429|overloaded|capacity|disconnect|connection[a-z ]*|ECONN[A-Z]*|ETIMEDOUT|timed? ?out|temporarily|internal server error|50[023]|529' ${ERRTXT[@]+"${ERRTXT[@]}"} | head -1)" >&2
    sleep "$wait_s"
    continue
  fi
  echo "subfleet claude: FAILED rc=$rc attempt=$attempt lane=$EMAIL (out_bytes=$(wc -c <"$OUT_FILE" | tr -d ' ')); stderr tail:" >&2
  tail -3 "$ERR_FILE" >&2
  [ -s "$RAW_FILE" ] && jq -r '.result // empty' "$RAW_FILE" 2>/dev/null | tail -2 >&2
  [ "$rc" -eq 0 ] && rc=1
  case "$rc" in 4|5) rc=1 ;; esac
  exit "$rc"
done
