Skip to content

fix(devices): local fallback for unknown requestId in password auth mode#100455

Closed
zy84338719 wants to merge 1 commit into
openclaw:mainfrom
zy84338719:fix/devices-approve-fallback-password-mode
Closed

fix(devices): local fallback for unknown requestId in password auth mode#100455
zy84338719 wants to merge 1 commit into
openclaw:mainfrom
zy84338719:fix/devices-approve-fallback-password-mode

Conversation

@zy84338719

Copy link
Copy Markdown

Problem

Fixes #59889

When the gateway uses password auth mode, openclaw devices approve <requestId> fails with unknown requestId even though the request appears in openclaw devices list. This creates an unresolvable deadlock where:

  1. Node-host reconnects after upgrade and submits a role-upgrade repair pairing request
  2. The requestId in the gateway's in-memory pending store gets refreshed (rotated) by repeated reconnection attempts
  3. openclaw devices list reads the pending request from local files (fallback path works)
  4. 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)
  5. The fallback in approvePairingWithFallback only triggers on pairing required connection errors (token auth mode), never on method-level unknown requestId errors
  6. User is stuck — no CLI path can approve the request

Root Cause

resolveLocalPairingFallback only fires for pairing required connection errors (WebSocket 1008). In password auth mode, the CLI successfully authenticates and reaches the gateway method handler, which returns a method-level unknown requestId error. The two isUnknownRequestIdError exit points in approvePairingWithFallback return null without attempting local file-based approval.

Fix

  1. Extract isLocalLoopbackGateway() — reusable helper that checks whether the CLI is targeting a local loopback gateway (previously inline in resolveLocalPairingFallback).

  2. Add tryLocalApproveForLoopback() — when the gateway returns unknown requestId on a loopback setup, this function:

    • Checks if the original requestId still exists in local pending.json → approves it locally
    • If not, searches for a same-device replacement request (the node-host refreshed it) → approves that instead
    • Returns undefined if neither is found (preserving existing return null behavior)
  3. Wire into both isUnknownRequestIdError exit paths in approvePairingWithFallback — the admin-retry catch block and the main fallback catch block.

Testing

Related Issues

Checklist

  • Code follows existing style conventions
  • No breaking changes to existing fallback paths
  • Local fallback only activates on loopback gateway (same security boundary as existing resolveLocalPairingFallback)
  • Commit message references issue #

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
Copilot AI review requested due to automatic review settings July 5, 2026 20:33
@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes size: S labels Jul 5, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 returns unknown requestId.
  • Wires the local-approve attempt into both unknown requestId exit paths in approvePairingWithFallback().

Comment on lines +404 to +411
// 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) {
Comment on lines +412 to +433
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),
};
}
Comment on lines +374 to 381
async function tryLocalApproveForLoopback(
opts: DevicesRpcOpts,
requestId: string,
originalRequest: PendingDevice | null,
): Promise<Record<string, unknown> | null | undefined> {
if (!isLocalLoopbackGateway(opts)) {
return undefined;
}
@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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 details

Best 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 device.pair.approve return method-level unknown requestId on an implicit loopback gateway after the pending request rotates, and current main returns null instead of local-approving. I did not run the live macOS/password-mode scenario in this read-only review.

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.

  • [medium] Replacement approval matches only deviceId — src/cli/devices-cli.runtime.ts:406
    The new local fallback can approve a different pending request from the same device without proving publicKey, roles, client metadata, scope envelope, and repair/paired compatibility, weakening the approval boundary for local loopback recovery.
    Confidence: 0.92

AGENTS.md: found and applied where relevant.

What I checked:

  • linked superseding PR: fix(cli): recover replaced device approvals #85342 (fix(cli): recover replaced device approvals) is merged at 2026-05-22T13:44:16Z.
  • cluster evidence: the durable review links that PR in the work cluster or recommended risk path.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • hxy91819: Authored the merged replaced-device approval recovery PR that introduced the current compatible replacement behavior this PR should reuse. (role: feature-history contributor; confidence: high; commits: 6ea907cec1b3; files: src/cli/devices-cli.runtime.ts, src/cli/devices-cli.test.ts, src/infra/device-pairing-churn.test.ts)
  • welfo-beo: Recently changed the devices CLI around node approval guidance, admin retry, and unknown device approval handling in merged PR fix: surface node approval guidance from devices CLI #98115. (role: recent area contributor; confidence: medium; commits: fc97e92ddd31; files: src/cli/devices-cli.runtime.ts, src/cli/devices-cli.test.ts)
  • RomneyDa: Recently changed device approve deadlock guidance and unknown requestId recovery messaging in merged PR fix(cli): explain how to recover from device approve deadlock #98146. (role: recent area contributor; confidence: medium; commits: 9003042c5fa0; files: src/cli/devices-cli.runtime.ts, src/cli/devices-cli.test.ts)
  • steipete: Closed the linked bug after prior Codex review and appears in recent CLI-file history and issue cleanup around this device approval recovery area. (role: recent reviewer and adjacent owner; confidence: medium; commits: 23b4f3319593, cb5d43ba95ca; files: src/cli/devices-cli.runtime.ts, src/cli/devices-cli.test.ts)

Codex review notes: model internal, reasoning high; reviewed against 5e0504aa437e.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P0 Emergency: data loss, security bypass, crash loop, or unusable core runtime. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 5, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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 deviceId, silently absorbs a forbidden result, and does not include regression coverage. The follow-up reuses the stricter existing compatibility gate, surfaces forbidden failures, and adds the focused test/live-proof work.

This does not mean #100546 is merge-ready yet. Its current head can still turn a non-loopback missing scope: operator.pairing authorization failure into a null/no-request result. It needs to preserve or rethrow that auth error, add an explicit remote-URL missing-scope regression, rebase current main, and receive auth/CLI owner acceptance.

Consolidating on the safer follow-up avoids maintaining two competing versions of the same fallback.

@steipete steipete closed this Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P0 Emergency: data loss, security bypass, crash loop, or unusable core runtime. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

devices approve deadlock after gateway restart — node-host repair request cannot be approved via CLI

3 participants