mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 05:54:09 +08:00
Closes #135028 ## What Problem This Solves Agents → Tools → GitHub Identity could start a GitHub device authorization when the Gateway host had no `gh` executable. The operator received a valid code even though later GitHub operations could not run. ## Root Cause The system, agent, and personal authorization owners requested a device code before checking their required local executable. ## Fix - Share one GitHub CLI availability check. - Run it at both lifecycle boundaries before device-code or pending-state creation. - Return installation guidance only for the typed missing-CLI failure. - Keep unrelated authorization failures bounded and generic. - Re-probe on each explicit retry so an install or removal under the same `PATH` is recognized immediately. ## Evidence - Baseline main `ff6fb73f01205480c15c2863cd04ea0539380b2f`: the real CLI and isolated Gateway returned a device URL and user code without `gh`. Public run: https://github.com/openclaw/openclaw/actions/runs/33642962313 - Exact candidate `70d15484b198efbd6c804f0c73fc3af9ae362cf1`, tree `64942dc7265acba1490bcbd9531cfdcdb7ed1f86`: sanitized direct AWS proof passed 97 focused Gateway tests. - The real agent-scope CLI → isolated Gateway E2E passed and returned installation guidance with no device URL or user code. - The exact-tree changed-scope gate passed, including TypeScript, lint, dead-export, database, and import-cycle checks. - Oxfmt 0.60.0 and `git diff --check` passed. ## Surface - Production: +31/-2 lines, net +29. - Tests: +140/-0 lines. - The production growth adds the typed prerequisite boundary and safe operator-visible recovery message. ## ClawSweeper Rank-up Move - Skip further production-line reduction. One shared helper owns the check, but both lifecycle calls must remain: the personal owner creates pending state before the shared device transport, while the system/agent owner has a separate record boundary. Moving the check later reintroduces the bug. Co-authored-by: Ayaan Zaidi <hi@obviy.us>
50 lines
1.6 KiB
TypeScript
50 lines
1.6 KiB
TypeScript
// E2E: a Gateway host without gh rejects authorization before device-code issuance.
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { resolveExecutablePath } from "../src/infra/executable-path.js";
|
|
import {
|
|
createOpenClawTestInstance,
|
|
type OpenClawTestInstance,
|
|
} from "./helpers/openclaw-test-instance.js";
|
|
|
|
const TEST_TIMEOUT_MS = 180_000;
|
|
const instances: OpenClawTestInstance[] = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.allSettled(instances.splice(0).map((instance) => instance.cleanup()));
|
|
});
|
|
|
|
describe("GitHub CLI authorization preflight", () => {
|
|
it(
|
|
"returns installation guidance through the real CLI and Gateway without requesting a code",
|
|
{ timeout: TEST_TIMEOUT_MS },
|
|
async () => {
|
|
const gatewayPath = path.dirname(process.execPath);
|
|
expect(resolveExecutablePath("gh", { env: { PATH: gatewayPath } })).toBeUndefined();
|
|
|
|
const instance = await createOpenClawTestInstance({
|
|
name: "github-cli-preflight",
|
|
env: { PATH: gatewayPath, OPENCLAW_PATH_BOOTSTRAPPED: "1" },
|
|
});
|
|
instances.push(instance);
|
|
await instance.startGateway();
|
|
|
|
const result = await instance.cli([
|
|
"gateway",
|
|
"call",
|
|
"tools.github.authorize.start",
|
|
"--params",
|
|
'{"scope":"agent","agentId":"main"}',
|
|
"--json",
|
|
]);
|
|
|
|
const output = `${result.stdout}\n${result.stderr}`;
|
|
expect(output).toContain(
|
|
"GitHub CLI (`gh`) is required on the Gateway host. Install it and retry.",
|
|
);
|
|
expect(output).not.toContain("github.com/login/device");
|
|
expect(output).not.toContain("userCode");
|
|
},
|
|
);
|
|
});
|