diff --git a/CHANGELOG.md b/CHANGELOG.md index 33896d3c26b..3206ba981a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Cron: keep long manual cron runs active in the task registry until completion, preventing transient `lost` markers before durable recovery reconciles. Fixes #78233. (#78243) Thanks @Feelw00. +- Doctor/GitHub CLI: surface a `GH_CONFIG_DIR` hint when the GitHub skill is usable but `gh` auth lives under a different operator HOME than the agent process, without warning for disabled or filtered skills. Fixes #78063. (#78095) Thanks @tmimmanuel. - Gateway: clear speculative node wake state when APNs registration is missing, preventing unregistered or mistyped node IDs from retaining wake throttle entries. Fixes #68847. (#68848) Thanks @Feelw00. - Auto-reply: keep late follow-up queue drain finalizers from deleting a replacement queue registered after `/stop`, preventing immediate follow-up messages from being orphaned. Fixes #68838. (#68839) Thanks @Feelw00. - Feishu: make manual App ID/App Secret setup the default channel-binding path while keeping QR scan-to-create as an optional best-effort flow, and document the manual fallback for domestic Feishu mobile clients that do not react to the QR code. Fixes #80591. Thanks @wei-wei-zhao. diff --git a/src/agents/skills/gh-config-discovery.test.ts b/src/agents/skills/gh-config-discovery.test.ts index bfcf5050f1e..40b9a2becbb 100644 --- a/src/agents/skills/gh-config-discovery.test.ts +++ b/src/agents/skills/gh-config-discovery.test.ts @@ -142,6 +142,20 @@ describe("detectGhConfigDirMismatch", () => { }); }); + it("respects XDG_CONFIG_HOME before HOME on darwin", () => { + const result = detectGhConfigDirMismatch( + makeInput({ + platform: "darwin", + env: { HOME: "/Users/agent", XDG_CONFIG_HOME: "/Users/agent/Library/XDG" }, + fileExists: fileSet("/Users/agent/Library/XDG/gh/hosts.yml"), + }), + ); + expect(result).toEqual({ + kind: "auth-discoverable", + effectiveConfigDir: "/Users/agent/Library/XDG/gh", + }); + }); + it("uses HOME/.config/gh on darwin (matches gh's documented macOS lookup)", () => { const result = detectGhConfigDirMismatch( makeInput({ @@ -173,6 +187,37 @@ describe("detectGhConfigDirMismatch", () => { }); }); + it("respects XDG_CONFIG_HOME before APPDATA on win32", () => { + const result = detectGhConfigDirMismatch( + makeInput({ + platform: "win32", + env: { + XDG_CONFIG_HOME: "C:\\Users\\agent\\XDG", + APPDATA: "C:\\Users\\agent\\AppData\\Roaming", + }, + fileExists: fileSet("C:\\Users\\agent\\XDG\\gh\\hosts.yml"), + }), + ); + expect(result).toMatchObject({ + kind: "auth-discoverable", + effectiveConfigDir: "C:\\Users\\agent\\XDG\\gh", + }); + }); + + it("falls back to HOME/.config/gh on win32 when APPDATA and USERPROFILE are missing", () => { + const result = detectGhConfigDirMismatch( + makeInput({ + platform: "win32", + env: { HOME: "C:\\Users\\agent" }, + fileExists: fileSet("C:\\Users\\agent\\.config\\gh\\hosts.yml"), + }), + ); + expect(result).toMatchObject({ + kind: "auth-discoverable", + effectiveConfigDir: "C:\\Users\\agent\\.config\\gh", + }); + }); + it("respects an explicit candidateOperatorHomes list", () => { const result = detectGhConfigDirMismatch( makeInput({ diff --git a/src/agents/skills/gh-config-discovery.ts b/src/agents/skills/gh-config-discovery.ts index 6c8404ca182..414982a574a 100644 --- a/src/agents/skills/gh-config-discovery.ts +++ b/src/agents/skills/gh-config-discovery.ts @@ -55,15 +55,16 @@ export type GhConfigDiscoveryResult = const HOSTS_FILE = "hosts.yml"; -// gh config-dir lookup order, matching the documented behavior of `gh -// help environment` for each platform. macOS uses Library/Application Support, -// Windows uses %AppData%\GitHub CLI, Linux/other uses XDG_CONFIG_HOME or -// $HOME/.config/gh. +// gh config-dir lookup order, matching `gh help environment`. function resolveEffectiveGhConfigDir(input: GhConfigDiscoveryInput): string | undefined { const env = input.env; if (env.GH_CONFIG_DIR && env.GH_CONFIG_DIR.trim()) { return env.GH_CONFIG_DIR.trim(); } + const xdg = env.XDG_CONFIG_HOME?.trim(); + if (xdg) { + return pathFor(input.platform).join(xdg, "gh"); + } if (input.platform === "win32") { const appData = env.APPDATA?.trim(); if (appData) { @@ -73,19 +74,6 @@ function resolveEffectiveGhConfigDir(input: GhConfigDiscoveryInput): string | un if (profile) { return pathFor(input.platform).join(profile, "AppData", "Roaming", "GitHub CLI"); } - return undefined; - } - if (input.platform === "darwin") { - const home = env.HOME?.trim(); - if (!home) { - return undefined; - } - return pathFor(input.platform).join(home, ".config", "gh"); - } - // Linux and POSIX-like default - const xdg = env.XDG_CONFIG_HOME?.trim(); - if (xdg) { - return pathFor(input.platform).join(xdg, "gh"); } const home = env.HOME?.trim(); if (!home) { diff --git a/src/commands/doctor-skills.test.ts b/src/commands/doctor-skills.test.ts index 682d92eb4e3..0a0ccf0aec7 100644 --- a/src/commands/doctor-skills.test.ts +++ b/src/commands/doctor-skills.test.ts @@ -124,6 +124,40 @@ describe("doctor skills", () => { expect(describeGhConfigDirHintFromDiscovery([githubSkill], discovery)).toEqual([]); }); + it("does not surface the GH_CONFIG_DIR hint when the github skill is disabled", () => { + const githubSkill = createSkill({ + name: "github", + skillKey: "github", + eligible: false, + disabled: true, + missing: { bins: [], anyBins: [], env: [], config: [], os: [] }, + }); + const discovery: GhConfigDiscoveryInput = { + platform: "linux", + env: { HOME: "/agent/home" }, + fileExists: (p) => p === "/root/.config/gh/hosts.yml", + }; + + expect(describeGhConfigDirHintFromDiscovery([githubSkill], discovery)).toEqual([]); + }); + + it("does not surface the GH_CONFIG_DIR hint when the github skill is filtered out for the agent", () => { + const githubSkill = createSkill({ + name: "github", + skillKey: "github", + eligible: true, + blockedByAgentFilter: true, + missing: { bins: [], anyBins: [], env: [], config: [], os: [] }, + }); + const discovery: GhConfigDiscoveryInput = { + platform: "linux", + env: { HOME: "/agent/home" }, + fileExists: (p) => p === "/root/.config/gh/hosts.yml", + }; + + expect(describeGhConfigDirHintFromDiscovery([githubSkill], discovery)).toEqual([]); + }); + it("does not surface the GH_CONFIG_DIR hint when GH_CONFIG_DIR is already set", () => { const githubSkill = createSkill({ name: "github", diff --git a/src/commands/doctor-skills.ts b/src/commands/doctor-skills.ts index b365a7969ea..5fe19dc1411 100644 --- a/src/commands/doctor-skills.ts +++ b/src/commands/doctor-skills.ts @@ -70,9 +70,12 @@ export function describeGhConfigDirHintFromDiscovery( if (!githubSkill) { return []; } - // The github skill only requires the `gh` binary; if it is not installed we - // do not surface a config-dir hint (the bin install hint covers it). - if (githubSkill.missing.bins.includes("gh")) { + if ( + !githubSkill.eligible || + githubSkill.blockedByAgentFilter || + githubSkill.disabled || + githubSkill.blockedByAllowlist + ) { return []; } const result: GhConfigDiscoveryResult = detectGhConfigDirMismatch(discoveryInput);