CLI: restore and harden qr --remote pairing behavior (#18166)
Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: a79fc2a3c69234cdad1635c0cd25669fcdb4e11b Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Auto-reply/TTS: keep tool-result media delivery enabled in group chats and native command sessions (while still suppressing tool summary text) so `NO_REPLY` follow-ups do not drop successful TTS audio. (#17991) Thanks @zerone0x.
|
||||
- Cron: preserve per-job schedule-error isolation in post-run maintenance recompute so malformed sibling jobs no longer abort persistence of successful runs. (#17852) Thanks @pierreeurope.
|
||||
- CLI/Pairing: make `openclaw qr --remote` prefer `gateway.remote.url` over tailscale/public URL resolution and register the `openclaw clawbot qr` legacy alias path. (#18091)
|
||||
- CLI/QR: restore fail-fast validation for `openclaw qr --remote` when neither `gateway.remote.url` nor tailscale `serve`/`funnel` is configured, preventing unusable remote pairing QR flows. (#18166) Thanks @mbelinky.
|
||||
|
||||
## 2026.2.15
|
||||
|
||||
|
||||
@@ -183,6 +183,24 @@ describe("registerQrCli", () => {
|
||||
expect(payload.urlSource).toBe("gateway.remote.url");
|
||||
});
|
||||
|
||||
it("errors when --remote is set but no remote URL is configured", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
gateway: {
|
||||
bind: "custom",
|
||||
customBindHost: "gateway.local",
|
||||
auth: { mode: "token", token: "tok" },
|
||||
},
|
||||
});
|
||||
|
||||
const program = new Command();
|
||||
registerQrCli(program);
|
||||
|
||||
await expect(program.parseAsync(["qr", "--remote"], { from: "user" })).rejects.toThrow("exit");
|
||||
|
||||
const output = runtime.error.mock.calls.map((call) => String(call[0] ?? "")).join("\n");
|
||||
expect(output).toContain("qr --remote requires");
|
||||
});
|
||||
|
||||
it("prefers gateway.remote.url over tailscale when --remote is set", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
gateway: {
|
||||
|
||||
@@ -95,6 +95,17 @@ export function registerQrCli(program: Command) {
|
||||
cfg.gateway.auth.token = undefined;
|
||||
}
|
||||
}
|
||||
if (wantsRemote && !opts.url && !opts.publicUrl) {
|
||||
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
||||
const remoteUrl = cfg.gateway?.remote?.url;
|
||||
const hasRemoteUrl = typeof remoteUrl === "string" && remoteUrl.trim().length > 0;
|
||||
const hasTailscaleServe = tailscaleMode === "serve" || tailscaleMode === "funnel";
|
||||
if (!hasRemoteUrl && !hasTailscaleServe) {
|
||||
throw new Error(
|
||||
"qr --remote requires gateway.remote.url (or gateway.tailscale.mode=serve/funnel).",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const explicitUrl =
|
||||
typeof opts.url === "string" && opts.url.trim()
|
||||
|
||||
Reference in New Issue
Block a user