mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(cli): stop devices approve from misdiagnosing a superseded pairing request (#124637)
When the local pairing fallback could not find the gateway's requestId, the error always blamed a profile/state-dir mismatch and prescribed --token/--password — even on gateways with no shared auth, and even though a populated local pending list proves the CLI and gateway share one store and the held id was merely superseded by a re-minted request. The supersession case now names the current pending requestId with the exact approve command; the mismatch hypothesis and shared-auth flags remain only for an empty local pending list, phrased as possibilities. The fail-closed replacement-validation paths keep the mismatch wording so they never point at an incompatible pending request as a recovery step.
This commit is contained in:
committed by
GitHub
parent
a5f9adf483
commit
a3099a572d
@@ -330,16 +330,28 @@ function resolveLocalPairingFallback(
|
||||
}
|
||||
}
|
||||
|
||||
function buildFallbackStateMismatchError(details: ConnectPairingRequiredDetails): Error {
|
||||
return new Error(
|
||||
[
|
||||
details.requestId
|
||||
? `${FALLBACK_STATE_MISMATCH_MESSAGE} Missing requestId: ${details.requestId}.`
|
||||
: FALLBACK_STATE_MISMATCH_MESSAGE,
|
||||
"The running gateway is probably using a different OPENCLAW_PROFILE or OPENCLAW_STATE_DIR than this CLI.",
|
||||
"Rerun with the same profile/state-dir as the gateway, or pass --token/--password so the CLI can approve through the gateway.",
|
||||
].join("\n"),
|
||||
);
|
||||
function buildFallbackStateMismatchError(
|
||||
details: ConnectPairingRequiredDetails,
|
||||
pendingRequestIds: string[],
|
||||
): Error {
|
||||
const heading = details.requestId
|
||||
? `${FALLBACK_STATE_MISMATCH_MESSAGE} Missing requestId: ${details.requestId}.`
|
||||
: FALLBACK_STATE_MISMATCH_MESSAGE;
|
||||
// A populated local pending list means the CLI and gateway share this store:
|
||||
// each rejected connect re-mints the request, so the held id is stale rather
|
||||
// than foreign. Only an empty list suggests a genuinely different store, and
|
||||
// shared-auth flags are only a fix when the gateway actually uses shared auth.
|
||||
const guidance =
|
||||
pendingRequestIds.length > 0
|
||||
? [
|
||||
"That request was superseded by a newer pending request.",
|
||||
`Approve the current request instead: openclaw devices approve ${pendingRequestIds[0]}`,
|
||||
]
|
||||
: [
|
||||
"The running gateway may be using a different OPENCLAW_PROFILE or OPENCLAW_STATE_DIR than this CLI.",
|
||||
"Rerun with the gateway's profile/state-dir; if the gateway uses shared auth, pass --token/--password to approve through it.",
|
||||
];
|
||||
return new Error([heading, ...guidance].join("\n"));
|
||||
}
|
||||
|
||||
function assertLocalFallbackMatchesGatewayRequest(
|
||||
@@ -350,11 +362,11 @@ function assertLocalFallbackMatchesGatewayRequest(
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
const hasRequest = (list.pending ?? []).some(
|
||||
(request) => normalizeOptionalString(request.requestId) === requestId,
|
||||
);
|
||||
if (!hasRequest) {
|
||||
throw buildFallbackStateMismatchError(details);
|
||||
const pendingRequestIds = (list.pending ?? [])
|
||||
.map((request) => normalizeOptionalString(request.requestId))
|
||||
.filter((id): id is string => Boolean(id));
|
||||
if (!pendingRequestIds.includes(requestId)) {
|
||||
throw buildFallbackStateMismatchError(details, pendingRequestIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +485,9 @@ async function approvePairingWithFallback(
|
||||
if (!hasOriginalPending && !hasGatewayPending) {
|
||||
return null;
|
||||
}
|
||||
throw buildFallbackStateMismatchError(fallback.details);
|
||||
// Fail-closed replacement validation refused to substitute; do not point
|
||||
// at the incompatible pending id as a recovery step.
|
||||
throw buildFallbackStateMismatchError(fallback.details, []);
|
||||
}
|
||||
const approved = await approveDevicePairing(requestId, {
|
||||
// Local CLI fallback already assumes direct machine access; treat it as an
|
||||
@@ -482,7 +496,7 @@ async function approvePairingWithFallback(
|
||||
});
|
||||
if (!approved) {
|
||||
if (gatewayRequestId && gatewayRequestId === requestId) {
|
||||
throw buildFallbackStateMismatchError(fallback.details);
|
||||
throw buildFallbackStateMismatchError(fallback.details, []);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -992,7 +992,7 @@ describe("devices cli local fallback", () => {
|
||||
expect(readRuntimeOutput()).toContain(fallbackNotice);
|
||||
});
|
||||
|
||||
it("refuses local fallback when the gateway request is absent from local pairing state", async () => {
|
||||
it("points at the current pending request when the gateway request id went stale", async () => {
|
||||
rejectGatewayForLocalFallback("scope upgrade pending approval (requestId: req-profile)");
|
||||
listDevicePairing.mockResolvedValueOnce({
|
||||
pending: [{ requestId: "req-default", deviceId: "device-1", publicKey: "pk", ts: 1 }],
|
||||
@@ -1000,9 +1000,18 @@ describe("devices cli local fallback", () => {
|
||||
});
|
||||
summarizeDeviceTokens.mockReturnValue(undefined);
|
||||
|
||||
await expect(runDevicesCommand(["list"])).rejects.toThrow(
|
||||
"different OPENCLAW_PROFILE or OPENCLAW_STATE_DIR",
|
||||
// A populated shared pending list means supersession, not a foreign state
|
||||
// dir — the recovery is the current id, never profile or shared-auth flags.
|
||||
const failure = await runDevicesCommand(["list"]).then(
|
||||
() => {
|
||||
throw new Error("expected devices list to fail");
|
||||
},
|
||||
(error: unknown) => String(error),
|
||||
);
|
||||
expect(failure).toContain("superseded by a newer pending request");
|
||||
expect(failure).toContain("openclaw devices approve req-default");
|
||||
expect(failure).not.toContain("OPENCLAW_PROFILE");
|
||||
expect(failure).not.toContain("--token");
|
||||
expect(readRuntimeOutput()).not.toContain(fallbackNotice);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user