From 78c72f49058c55415c3806be33afc431395f3689 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 22 Feb 2026 06:31:04 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20oc-scope-watchdog=20=E2=80=94=20auto-re?= =?UTF-8?q?stores=20operator=20scopes=20after=20gateway=20restart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/restore-oc-scopes.py | 5 +-- scripts/scope-watchdog.py | 68 ++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 scripts/scope-watchdog.py diff --git a/scripts/restore-oc-scopes.py b/scripts/restore-oc-scopes.py index d3cc2af..b42a176 100755 --- a/scripts/restore-oc-scopes.py +++ b/scripts/restore-oc-scopes.py @@ -5,8 +5,9 @@ Run after every gateway start to work around OC stripping scopes on restart. """ import json, glob, sys, os, time -# Give gateway a moment to write its files -time.sleep(2) +# Wait for gateway to fully initialize and write its files +# (Gateway writes device-auth.json during startup, overwriting scopes) +time.sleep(10) BASE = os.path.expanduser('~/.openclaw') SCOPES = ['operator.write', 'operator.read'] diff --git a/scripts/scope-watchdog.py b/scripts/scope-watchdog.py new file mode 100644 index 0000000..653d53f --- /dev/null +++ b/scripts/scope-watchdog.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +""" +Watches OpenClaw device-auth.json and restores operator scopes when stripped. +Runs as a persistent systemd service alongside openclaw-gateway. +""" +import json, glob, os, time, subprocess, sys + +BASE = os.path.expanduser('~/.openclaw') +DEVICE_AUTH = f'{BASE}/identity/device-auth.json' +SCOPES = ['operator.write', 'operator.read'] +CHECK_INTERVAL = 30 # seconds + +def get_scopes(): + try: + with open(DEVICE_AUTH) as f: + return json.load(f).get('scopes') or [] + except: + return None + +def restore_scopes(): + fixed = [] + # Fix device-auth.json + try: + with open(DEVICE_AUTH) as f: + d = json.load(f) + if d.get('scopes') != SCOPES: + d['scopes'] = SCOPES + with open(DEVICE_AUTH, 'w') as f: + json.dump(d, f, indent=2) + fixed.append('device-auth.json') + except Exception as e: + print(f'[scope-watchdog] device-auth error: {e}', file=sys.stderr) + + # Fix devices/*.json + for p in glob.glob(f'{BASE}/devices/*.json'): + try: + with open(p) as f: + data = json.load(f) + changed = False + items = data if isinstance(data, list) else [data] + for item in items: + if isinstance(item, dict) and item.get('scopes') != SCOPES: + item['scopes'] = SCOPES + changed = True + if changed: + with open(p, 'w') as f: + json.dump(data, f, indent=2) + fixed.append(os.path.basename(p)) + except: + pass + + return fixed + +print('[scope-watchdog] Starting. Checking every 30s.', flush=True) + +# Initial delay to let gateway fully start +time.sleep(15) + +while True: + scopes = get_scopes() + if scopes is None: + print('[scope-watchdog] device-auth.json not found, waiting...', flush=True) + elif scopes != SCOPES: + print(f'[scope-watchdog] Scopes stripped ({scopes}), restoring...', flush=True) + fixed = restore_scopes() + if fixed: + print(f'[scope-watchdog] Restored scopes in: {fixed}', flush=True) + time.sleep(CHECK_INTERVAL)