Spaces:
Running
Running
fix: patch OpenClaw scope-clearing bug for A2A dispatch (#17187)
Browse filesWhen 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>
- 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 |
-
#
|
| 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 |
-
|
| 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}")
|