From f5f11b8d0e963ca9227af58ad996f4dbdfa17123 Mon Sep 17 00:00:00 2001 From: Clawdbot Date: Tue, 5 May 2026 14:39:55 +1000 Subject: [PATCH] fix(doctor): avoid impossible device token rotation advice --- src/commands/doctor-device-pairing.test.ts | 23 ++++++++++++++++++++++ src/commands/doctor-device-pairing.ts | 20 +++++++++---------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/commands/doctor-device-pairing.test.ts b/src/commands/doctor-device-pairing.test.ts index 92ade27fcfe..7b7667b998c 100644 --- a/src/commands/doctor-device-pairing.test.ts +++ b/src/commands/doctor-device-pairing.test.ts @@ -170,6 +170,29 @@ describe("noteDevicePairingHealth", () => { }); }); + it("does not suggest rotating local auth for a role that is no longer approved", async () => { + await withApprovedOperatorPairing(async ({ identity }) => { + storeDeviceAuthToken({ + deviceId: identity.deviceId, + role: "node", + token: "stale-node-token", + scopes: [], + }); + + await noteDevicePairingHealth({ + cfg: { gateway: { mode: "local" } }, + healthOk: false, + }); + + expect(noteMock).toHaveBeenCalledTimes(1); + const message = String(noteMock.mock.calls[0]?.[0] ?? ""); + expect(message).toContain("Local cached node device auth"); + expect(message).toContain("role is no longer approved"); + expect(message).toContain("remove the stale cached node auth entry"); + expect(message).not.toContain("--role node"); + }); + }); + it("uses gateway device pairing state when the gateway is healthy", async () => { callGatewayMock.mockResolvedValue({ pending: [ diff --git a/src/commands/doctor-device-pairing.ts b/src/commands/doctor-device-pairing.ts index ecf5607b7f4..b9c9722f3f0 100644 --- a/src/commands/doctor-device-pairing.ts +++ b/src/commands/doctor-device-pairing.ts @@ -474,6 +474,16 @@ function collectLocalDeviceAuthIssues(snapshot: DoctorPairingSnapshot): string[] if (!role) { continue; } + const pairedToken = findTokenSummary(paired, role); + if (!pairedToken) { + if (approvedRoles.has(role)) { + continue; + } + lines.push( + `- Local cached ${role} device auth for ${deviceLabel} no longer has a matching active gateway token, and that role is no longer approved for this device. Reconnect with shared gateway auth to refresh local auth, or remove the stale cached ${role} auth entry.`, + ); + continue; + } const rotateCommand = formatCliArgs([ "openclaw", "devices", @@ -483,16 +493,6 @@ function collectLocalDeviceAuthIssues(snapshot: DoctorPairingSnapshot): string[] "--role", role, ]); - const pairedToken = findTokenSummary(paired, role); - if (!pairedToken) { - if (approvedRoles.has(role)) { - continue; - } - lines.push( - `- Local cached ${role} device auth for ${deviceLabel} no longer has a matching active gateway token. Reconnect with shared gateway auth to refresh it, or rotate with ${rotateCommand}.`, - ); - continue; - } const gatewayIssuedAtMs = pairedToken.rotatedAtMs ?? pairedToken.createdAtMs; if (entry.updatedAtMs < gatewayIssuedAtMs) { lines.push(