fix(devices): local fallback for unknown requestId in password auth mode#100455
fix(devices): local fallback for unknown requestId in password auth mode#100455zy84338719 wants to merge 1 commit into
Conversation
When gateway uses password auth mode, the CLI connects successfully but the pending pairing request may have been refreshed by node-host reconnect before approval lands. This produces a method-level 'unknown requestId' error instead of the connection-level 'pairing required' error that resolveLocalPairingFallback expects. The existing fallback path in approvePairingWithFallback only triggers on 'pairing required' connection errors (token auth mode). In password auth mode, the fallback never fires and the user sees an unresolvable 'unknown requestId' deadlock. Changes: - Extract isLocalLoopbackGateway() helper from resolveLocalPairingFallback - Add tryLocalApproveForLoopback() to attempt file-based approval when gateway returns unknown requestId on a loopback setup - Wire it into both unknown-requestId exit paths in approvePairingWithFallback (admin-retry and main catch) - tryLocalApproveForLoopback handles both cases: 1. Original requestId still exists in local pending.json 2. Node-host already refreshed it (finds same-device replacement) Fixes openclaw#59889 Related openclaw#31041 openclaw#37749
There was a problem hiding this comment.
Pull request overview
Fixes a deadlock in openclaw devices approve <requestId> when using password auth mode against a local loopback gateway, where the gateway can return a method-level unknown requestId even though the pending request still exists (or has been rotated) in local pairing files.
Changes:
- Extracts
isLocalLoopbackGateway(opts)to centralize the “safe to use local pairing files” gate. - Adds
tryLocalApproveForLoopback()to attempt local file-based approval when the gateway returnsunknown requestId. - Wires the local-approve attempt into both
unknown requestIdexit paths inapprovePairingWithFallback().
| // Search for a replacement request from the same device. | ||
| if (originalRequest) { | ||
| const sameDeviceReplacement = localPending.find( | ||
| (req) => | ||
| req.deviceId === originalRequest.deviceId && | ||
| req.requestId !== requestId, | ||
| ); | ||
| if (sameDeviceReplacement) { |
| const approved = await approveDevicePairing(sameDeviceReplacement.requestId, { | ||
| callerScopes: ["operator.admin"], | ||
| }); | ||
| if (approved && approved.status !== "forbidden") { | ||
| if (opts.json !== true) { | ||
| defaultRuntime.log( | ||
| theme.warn( | ||
| `Pending request ${sanitizeForLog(requestId)} was replaced by same-device repair ${sanitizeForLog(sameDeviceReplacement.requestId)}; approving latest compatible request.`, | ||
| ), | ||
| ); | ||
| defaultRuntime.log(theme.warn(FALLBACK_NOTICE)); | ||
| } | ||
| return { | ||
| requestId: sameDeviceReplacement.requestId, | ||
| resolved: { | ||
| kind: "same-device-replacement" as const, | ||
| requestedRequestId: requestId, | ||
| approvedRequestId: sameDeviceReplacement.requestId, | ||
| }, | ||
| device: redactLocalPairedDevice(approved.device), | ||
| }; | ||
| } |
| async function tryLocalApproveForLoopback( | ||
| opts: DevicesRpcOpts, | ||
| requestId: string, | ||
| originalRequest: PendingDevice | null, | ||
| ): Promise<Record<string, unknown> | null | undefined> { | ||
| if (!isLocalLoopbackGateway(opts)) { | ||
| return undefined; | ||
| } |
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Keep open: the PR targets a real device-approval recovery bug, but the submitted replacement-request fallback is not safe to merge because it can approve a different local pending request after matching only deviceId. Canonical path: Close this PR as superseded by #85342. So I’m closing this here and keeping the remaining discussion on #85342. Review detailsBest possible solution: Close this PR as superseded by #85342. Do we have a high-confidence way to reproduce the issue? Yes, source inspection gives a high-confidence reproduction path: make Is this the best way to solve the issue? No, not as submitted. The best fix is still the PR's narrow password-mode fallback idea, but replacement approval must reuse the existing compatibility gate and forbidden handling before it is safe. Security review: Security review needs attention: The diff introduces a concrete device-approval boundary concern by approving replacement requests with weaker matching than the existing fallback path.
AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 5e0504aa437e. |
|
Thanks @zy84338719 for iterating on this auth-sensitive recovery path. Closing this older branch as superseded by your stronger follow-up in #100546. This head can approve a replacement request after matching only This does not mean #100546 is merge-ready yet. Its current head can still turn a non-loopback Consolidating on the safer follow-up avoids maintaining two competing versions of the same fallback. |
Problem
Fixes #59889
When the gateway uses password auth mode,
openclaw devices approve <requestId>fails withunknown requestIdeven though the request appears inopenclaw devices list. This creates an unresolvable deadlock where:openclaw devices listreads the pending request from local files (fallback path works)openclaw devices approve <requestId>connects to the gateway via password auth (connection succeeds), but the gateway no longer recognizes the requestId (method-level error, not connection-level)approvePairingWithFallbackonly triggers onpairing requiredconnection errors (token auth mode), never on method-levelunknown requestIderrorsRoot Cause
resolveLocalPairingFallbackonly fires forpairing requiredconnection errors (WebSocket 1008). In password auth mode, the CLI successfully authenticates and reaches the gateway method handler, which returns a method-levelunknown requestIderror. The twoisUnknownRequestIdErrorexit points inapprovePairingWithFallbackreturnnullwithout attempting local file-based approval.Fix
Extract
isLocalLoopbackGateway()— reusable helper that checks whether the CLI is targeting a local loopback gateway (previously inline inresolveLocalPairingFallback).Add
tryLocalApproveForLoopback()— when the gateway returnsunknown requestIdon a loopback setup, this function:requestIdstill exists in localpending.json→ approves it locallyundefinedif neither is found (preserving existingreturn nullbehavior)Wire into both
isUnknownRequestIdErrorexit paths inapprovePairingWithFallback— the admin-retry catch block and the main fallback catch block.Testing
openclaw devices approvesuccessfully resolves via local fallback.Related Issues
openclaw node installdoes not includeOPENCLAW_GATEWAY_TOKENin LaunchAgent plist EnvironmentVariables #31041 —node installdoesn't persistOPENCLAW_GATEWAY_TOKENin LaunchAgent plistChecklist
resolveLocalPairingFallback)