tao-shen Claude Opus 4.6 (1M context) commited on
Commit
2e04f91
Β·
1 Parent(s): 99bd034

fix: patch OpenClaw scope-clearing bug for A2A dispatch (#17187)

Browse files

When dangerouslyDisableDeviceAuth=true, OpenClaw's gateway-cli JS sets
device=null then clears ALL scopes to []. This breaks A2A gateway dispatch
which requires operator.write.

Patch the compiled JS files at startup to replace scopes=[] with full
operator scopes, matching the community workaround from issue #17187.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. scripts/sync_hf.py +26 -43
scripts/sync_hf.py CHANGED
@@ -576,52 +576,11 @@ class OpenClawFullSync:
576
  target.write_text(text)
577
  print(f"[SYNC] Deployed workspace template: {tmpl.name}")
578
 
579
- # ── Pre-create device identity with operator.write scope ──────
580
- # OpenClaw's auto-pairing only grants operator.read, but A2A
581
- # gateway dispatch requires operator.write (known issue #22226).
582
- # Fix: pre-create device-auth.json and paired.json with full scopes.
583
- import uuid as _uuid
584
- device_id = "huggingclaw-a2a"
585
- device_token = GATEWAY_TOKEN
586
- full_scopes = ["operator.read", "operator.write", "operator.admin",
587
- "operator.approvals", "operator.pairing"]
588
-
589
- identity_dir = Path(OPENCLAW_HOME) / "identity"
590
- identity_dir.mkdir(parents=True, exist_ok=True)
591
- device_auth = identity_dir / "device-auth.json"
592
- device_auth.write_text(json.dumps({
593
- "clientId": device_id,
594
- "clientMode": "operator",
595
- "role": "operator",
596
- "scopes": full_scopes,
597
- "tokens": [{
598
- "role": "operator",
599
- "scopes": full_scopes,
600
- "token": device_token
601
- }]
602
- }, indent=2))
603
-
604
  devices_dir = Path(OPENCLAW_HOME) / "devices"
605
  if devices_dir.exists():
606
  shutil.rmtree(devices_dir, ignore_errors=True)
607
- devices_dir.mkdir(parents=True, exist_ok=True)
608
- paired_file = devices_dir / "paired.json"
609
- paired_file.write_text(json.dumps([{
610
- "id": device_id,
611
- "name": f"{AGENT_NAME} A2A Bridge",
612
- "role": "operator",
613
- "scopes": full_scopes,
614
- "tokens": {
615
- "operator": {
616
- "scopes": full_scopes,
617
- "token": device_token
618
- }
619
- },
620
- "createdAt": datetime.now().isoformat(),
621
- "approved": True,
622
- "paired": True
623
- }], indent=2))
624
- print(f"[SYNC] Pre-created device identity with operator.write scope")
625
 
626
  # Verify write
627
  with open(config_path, "r") as f:
@@ -679,6 +638,30 @@ class OpenClawFullSync:
679
  except: pass
680
  return None
681
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
  # Use subprocess.run with direct output, no shell pipe
683
  print(f"[SYNC] Launching: {' '.join(entry_cmd)}")
684
  print(f"[SYNC] Working directory: {APP_DIR}")
 
576
  target.write_text(text)
577
  print(f"[SYNC] Deployed workspace template: {tmpl.name}")
578
 
579
+ # Clean stale devices from backup
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  devices_dir = Path(OPENCLAW_HOME) / "devices"
581
  if devices_dir.exists():
582
  shutil.rmtree(devices_dir, ignore_errors=True)
583
+ print("[SYNC] Deleted devices/ dir to force fresh auto-pair")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
 
585
  # Verify write
586
  with open(config_path, "r") as f:
 
638
  except: pass
639
  return None
640
 
641
+ # ── Patch OpenClaw scope bug (openclaw/openclaw#17187) ────────
642
+ # When dangerouslyDisableDeviceAuth=true, OpenClaw clears ALL scopes
643
+ # (including operator.write needed for A2A dispatch).
644
+ # Fix: patch the JS files to grant full scopes instead of clearing.
645
+ dist_dir = Path(APP_DIR) / "dist"
646
+ if dist_dir.exists():
647
+ import glob as _glob
648
+ patched = 0
649
+ for js_file in dist_dir.glob("gateway-cli-*.js"):
650
+ try:
651
+ content = js_file.read_text()
652
+ # Find the scope-clearing pattern and replace with full scopes
653
+ old_pattern = "scopes=[]"
654
+ new_pattern = 'scopes=["operator.read","operator.write","operator.admin","operator.approvals","operator.pairing"]'
655
+ if old_pattern in content:
656
+ content = content.replace(old_pattern, new_pattern)
657
+ js_file.write_text(content)
658
+ patched += 1
659
+ print(f"[SYNC] Patched {js_file.name}: scope-clearing β†’ full operator scopes")
660
+ except Exception as e:
661
+ print(f"[SYNC] Failed to patch {js_file.name}: {e}")
662
+ if patched == 0:
663
+ print("[SYNC] No gateway-cli files needed patching (pattern not found or already patched)")
664
+
665
  # Use subprocess.run with direct output, no shell pipe
666
  print(f"[SYNC] Launching: {' '.join(entry_cmd)}")
667
  print(f"[SYNC] Working directory: {APP_DIR}")