Skip to content

fix: alias fallback when manifest arms lack side field (silent bimanual cal sync no-op) - #64

Open
Shiki42 wants to merge 1 commit into
MINT-SJTU:mainfrom
Shiki42:fix/bimanual-cal-dir-side-fallback
Open

fix: alias fallback when manifest arms lack side field (silent bimanual cal sync no-op)#64
Shiki42 wants to merge 1 commit into
MINT-SJTU:mainfrom
Shiki42:fix/bimanual-cal-dir-side-fallback

Conversation

@Shiki42

@Shiki42 Shiki42 commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • refresh_bimanual_cal_dirs silently no-op'd when manifest arms lacked the side field — _pair_arms_by_side raised, the outer try/except Exception swallowed it, and calibration/bimanual_{followers,leaders}/bimanual_{left,right}.json stopped refreshing after every recalibration. Bimanual mode then loaded stale per-arm calibration while single-arm mode (which reads <serial>.json directly) stayed correct.
  • _pair_arms_by_side now falls back to alias-keyword inference when all sides are empty/missing (the exact heuristic command/helpers.py:group_arms already uses). Malformed sides (mixed case, duplicated values) still surface via _validate_bimanual_arm_sides instead of being masked.
  • refresh_bimanual_cal_dirs no longer wraps work in try/except Exception: logger.warning(...). With the fallback in place, remaining errors are real bugs and must propagate.

Observed symptom

On host b: manifest.json had no side field on any of its 4 arms (legacy state). After re-calibrating the left follower, bimanual_followers/bimanual_left.json and bimanual_followers/bimanual_right.json ended up with identical content (md5 821d96…), both pointing at the left follower's per-serial source — the right follower's 5B61037326.json never reached the alias. Right-side shoulder_pan was grossly offset in bimanual mode while single-arm mode on either side alone was accurate. Root cause matches the description in docs/bimanual-left-right-swap-bug.md, but specifically the no-side branch that PR #32's strict-side fix didn't handle.

Test plan

  • pytest tests/test_manifest_bimanual_sync.py -xvs (6 passed)
    • explicit-side pairing (works regardless of input order)
    • alias fallback under reversed array order [right_follower, left_follower] with no side (the regression scenario)
    • ambiguous aliases raise ValueError
    • malformed explicit sides (Left, duplicated left) raise ValueError instead of silently falling back
    • alias=None raises ValueError instead of TypeError
    • end-to-end refresh_bimanual_cal_dirs with a pre-seeded stale alias file proves overwrite, not just creation
  • On a host with a no-side legacy manifest: re-calibrate one follower and confirm bimanual_* md5s match the per-serial sources of both arms.

🤖 Generated with Claude Code

refresh_bimanual_cal_dirs() called _pair_arms_by_side -> _validate_bimanual_arm_sides,
which strictly required every arm in a 2-arm role to carry side ∈ {"left","right"}.
On real machines whose manifest.json was written by older code paths the side field
is missing entirely; the validator raised, the outer try/except Exception in
refresh_bimanual_cal_dirs swallowed it, and bimanual_{left,right}.json under
calibration/bimanual_{followers,leaders}/ silently stopped refreshing after every
recalibration. Single-arm mode (which reads <serial>.json directly) kept working,
so bimanual mode silently loaded stale calibration — observed symptom on host b
was the right-side shoulder_pan visibly offset in bimanual mode while single-arm
mode was accurate.

Fix:
- _pair_arms_by_side: if all sides are empty/missing, fall back to alias keyword
  inference (the same heuristic command/helpers.py:group_arms already uses).
  When any side IS set, keep validating strictly via _validate_bimanual_arm_sides
  so malformed manifests (mixed case, duplicated sides) still surface as errors
  instead of being masked by the alias path.
- _pair_arms_by_alias: defensively coerce alias to "" so {"alias": None} produces
  a controlled ValueError, not a TypeError.
- refresh_bimanual_cal_dirs: drop the try/except Exception wrapper. With the
  alias fallback in place, remaining errors are real bugs and must propagate.
- _validate_bimanual_arm_sides is unchanged — _validate_arms still uses it for
  strict schema enforcement on new manifests.

Tests in tests/test_manifest_bimanual_sync.py cover: explicit-side pairing,
alias fallback (the exact reversed-array swap scenario from
docs/bimanual-left-right-swap-bug.md), malformed-side rejection, alias=None
handled as ValueError, and end-to-end refresh that overwrites a pre-seeded
stale alias file under a no-side manifest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant