CLI: restore and harden qr --remote pairing behavior (#18166)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: a79fc2a3c6
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:
Mariano
2026-02-16 15:38:07 +00:00
committed by GitHub
parent 1633c6fe98
commit 68e39cf2c3
3 changed files with 30 additions and 0 deletions

View File

@@ -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: {

View File

@@ -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()