mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 20:24:46 +00:00
fix: harden gh config discovery
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user