Skip to content

Commit 84f535c

Browse files
committed
fix(cli): preserve local pairing fallback for upgrades
1 parent 66c1190 commit 84f535c

4 files changed

Lines changed: 43 additions & 2 deletions

File tree

src/cli/devices-cli.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,22 @@ describe("devices cli local fallback", () => {
439439
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("Approved"));
440440
});
441441

442+
it("falls back to local pairing list when gateway returns a scope upgrade message on loopback", async () => {
443+
callGateway.mockRejectedValueOnce(
444+
new Error("scope upgrade pending approval (requestId: req-123)"),
445+
);
446+
listDevicePairing.mockResolvedValueOnce({
447+
pending: [{ requestId: "req-1", deviceId: "device-1", publicKey: "pk", ts: 1 }],
448+
paired: [],
449+
});
450+
summarizeDeviceTokens.mockReturnValue(undefined);
451+
452+
await runDevicesCommand(["list"]);
453+
454+
expect(listDevicePairing).toHaveBeenCalledTimes(1);
455+
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining(fallbackNotice));
456+
});
457+
442458
it("does not use local fallback when an explicit --url is provided", async () => {
443459
callGateway.mockRejectedValueOnce(new Error("gateway closed (1008): pairing required"));
444460

src/cli/devices-cli.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Command } from "commander";
22
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
33
import { isLoopbackHost } from "../gateway/net.js";
44
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../gateway/protocol/client-info.js";
5+
import { readConnectPairingRequiredMessage } from "../gateway/protocol/connect-error-details.js";
56
import {
67
approveDevicePairing,
78
formatDevicePairingForbiddenMessage,
@@ -120,7 +121,7 @@ function normalizeErrorMessage(error: unknown): string {
120121

121122
function shouldUseLocalPairingFallback(opts: DevicesRpcOpts, error: unknown): boolean {
122123
const message = normalizeLowercaseStringOrEmpty(normalizeErrorMessage(error));
123-
if (!message.includes("pairing required")) {
124+
if (!readConnectPairingRequiredMessage(message)) {
124125
return false;
125126
}
126127
if (typeof opts.url === "string" && opts.url.trim().length > 0) {

src/cli/logs-cli.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,29 @@ describe("logs cli", () => {
158158
expect(stderrWrites.join("")).toContain("reading local log file instead");
159159
});
160160

161+
it("falls back to the local log file on loopback scope-upgrade errors", async () => {
162+
callGatewayFromCli.mockRejectedValueOnce(
163+
new Error("scope upgrade pending approval (requestId: req-123)"),
164+
);
165+
readConfiguredLogTail.mockResolvedValueOnce({
166+
file: "/tmp/openclaw.log",
167+
cursor: 5,
168+
size: 5,
169+
lines: ["local fallback line"],
170+
truncated: false,
171+
reset: false,
172+
});
173+
174+
const stdoutWrites = captureStdoutWrites();
175+
const stderrWrites = captureStderrWrites();
176+
177+
await runLogsCli(["logs"]);
178+
179+
expect(readConfiguredLogTail).toHaveBeenCalledTimes(1);
180+
expect(stdoutWrites.join("")).toContain("local fallback line");
181+
expect(stderrWrites.join("")).toContain("reading local log file instead");
182+
});
183+
161184
describe("formatLogTimestamp", () => {
162185
it("formats UTC timestamp in plain mode by default", () => {
163186
const result = formatLogTimestamp("2025-01-01T12:00:00.000Z");

src/cli/logs-cli.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { setTimeout as delay } from "node:timers/promises";
22
import type { Command } from "commander";
33
import { buildGatewayConnectionDetails } from "../gateway/call.js";
44
import { isLoopbackHost } from "../gateway/net.js";
5+
import { readConnectPairingRequiredMessage } from "../gateway/protocol/connect-error-details.js";
56
import { formatErrorMessage } from "../infra/errors.js";
67
import { readConfiguredLogTail } from "../logging/log-tail.js";
78
import { parseLogLine } from "../logging/parse-log-line.js";
@@ -96,7 +97,7 @@ function normalizeErrorMessage(error: unknown): string {
9697

9798
function shouldUseLocalLogsFallback(opts: LogsCliOptions, error: unknown): boolean {
9899
const message = normalizeLowercaseStringOrEmpty(normalizeErrorMessage(error));
99-
if (!message.includes("pairing required")) {
100+
if (!readConnectPairingRequiredMessage(message)) {
100101
return false;
101102
}
102103
if (typeof opts.url === "string" && opts.url.trim().length > 0) {

0 commit comments

Comments
 (0)