fix(ai): harden chip mismatch fallback for unstable USB #12

Merged
electron-rare merged 1 commits from fix/guard-env-fallback-on-usb-instability into main 2026-02-21 18:24:09 +00:00
+81
View File
@@ -11,6 +11,9 @@ RTC_ENV_DEFAULT="${ZEROCLAW_RTC_PIO_ENV:-esp32dev}"
ZACUS_ENV_DEFAULT="${ZEROCLAW_ZACUS_PIO_ENV:-esp32dev}"
MONITOR_SECS_DEFAULT="${ZEROCLAW_MONITOR_SECS:-60}"
BAUD_DEFAULT="${ZEROCLAW_SERIAL_BAUD:-115200}"
ZEROCLAW_CHIP_MISMATCH_FALLBACK="${ZEROCLAW_CHIP_MISMATCH_FALLBACK:-1}"
ZEROCLAW_USB_STABILITY_SAMPLES="${ZEROCLAW_USB_STABILITY_SAMPLES:-2}"
ZEROCLAW_USB_STABILITY_DELAY="${ZEROCLAW_USB_STABILITY_DELAY:-1}"
usage() {
cat <<USAGE
@@ -103,6 +106,62 @@ env_exists() {
rg -q "^\[env:${env_name}\]" "$FW_DIR/platformio.ini"
}
serial_port_signature() {
local port="$1"
cd "$FW_DIR"
python3 - "$port" <<'PY'
import json
import sys
import subprocess
port = sys.argv[1]
raw = subprocess.check_output(["pio", "device", "list", "--json-output"], text=True)
if not raw or not raw.strip():
raise SystemExit(1)
try:
items = json.loads(raw)
except Exception:
raise SystemExit(1)
for it in items:
if str(it.get("port", "")) != port:
continue
print(f"{it.get('hwid','unknown')}|{it.get('description','unknown')}|{it.get('serial_number','')}")
raise SystemExit(0)
raise SystemExit(1)
PY
}
is_usb_port_stable() {
local port="$1"
local samples="$2"
local delay_secs="$3"
if (( samples < 2 )); then
return 0
fi
local i=0
local last_sig
local sig
while (( i < samples )); do
if ! sig="$(serial_port_signature "$port" 2>/dev/null || true)"; then
return 1
fi
if [[ "$i" -eq 0 ]]; then
last_sig="$sig"
elif [[ "$sig" != "$last_sig" ]]; then
return 1
fi
((i++))
if (( i < samples )); then
chatgpt-codex-connector[bot] commented 2026-02-21 18:24:06 +00:00 (Migrated from github.com)
Review

P1 Badge Treat failed signature reads as unstable

The stability guard never detects a failed probe because sig="$(serial_port_signature "$port" 2>/dev/null || true)" always exits successfully, so the if ! ...; then return 1 branch is effectively dead. When pio device list cannot return the target port (the exact unstable-USB case this patch targets), sig becomes empty for each sample and the function incorrectly reports the port as stable, allowing auto-fallback to proceed and masking link flapping.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Treat failed signature reads as unstable** The stability guard never detects a failed probe because `sig="$(serial_port_signature "$port" 2>/dev/null || true)"` always exits successfully, so the `if ! ...; then return 1` branch is effectively dead. When `pio device list` cannot return the target port (the exact unstable-USB case this patch targets), `sig` becomes empty for each sample and the function incorrectly reports the port as stable, allowing auto-fallback to proceed and masking link flapping. Useful? React with 👍 / 👎.
sleep "$delay_secs"
fi
done
return 0
}
if ! env_exists "$ENV_NAME"; then
echo "[fail] invalid PlatformIO env '${ENV_NAME}' for $target." >&2
echo "[info] valid envs:" >&2
@@ -219,6 +278,24 @@ resolve_env_fallback() {
return 1
}
can_use_fallback_on_mismatch() {
local failed_env="$1"
local fallback_env="$2"
if [[ "${ZEROCLAW_CHIP_MISMATCH_FALLBACK}" != "1" ]]; then
echo "[warn] chip mismatch detected for $failed_env -> $fallback_env, but ZEROCLAW_CHIP_MISMATCH_FALLBACK=${ZEROCLAW_CHIP_MISMATCH_FALLBACK} (auto-fallback disabled)."
return 1
fi
if ! is_usb_port_stable "$PORT" "$ZEROCLAW_USB_STABILITY_SAMPLES" "$ZEROCLAW_USB_STABILITY_DELAY"; then
echo "[warn] chip mismatch detected for $failed_env but serial port is unstable (flapping signature on $PORT)."
echo "[hint] Retry with a stable USB link or set ZEROCLAW_USB_STABILITY_SAMPLES=1 after manual validation."
return 1
fi
return 0
}
UPLOAD_ARGS=()
MONITOR_ARGS=()
if [[ -n "$PORT" ]]; then
@@ -240,6 +317,10 @@ UPLOAD_LAST_OUTPUT=""
if ! upload_once "$ACTIVE_ENV"; then
fallback_env="$(resolve_env_fallback "$ACTIVE_ENV" "$UPLOAD_LAST_OUTPUT" || true)"
if [[ -n "$fallback_env" ]]; then
if ! can_use_fallback_on_mismatch "$ACTIVE_ENV" "$fallback_env"; then
echo "[fail] upload skipped fallback for retry due to unstable link or disabled policy."
exit 3
fi
echo "[warn] detected chip/env mismatch for $ACTIVE_ENV; retrying with '$fallback_env'."
ACTIVE_ENV="$fallback_env"
echo "[step] rebuild (fallback env)"