Gateway: clarify launchctl domain bootstrap error (#13795)

This commit is contained in:
Vincent Koc
2026-02-19 02:03:23 -08:00
committed by GitHub
parent 88f698974a
commit be7462af1e
2 changed files with 82 additions and 1 deletions

View File

@@ -171,6 +171,67 @@ describe("launchd install", () => {
expect(plist).toContain("<key>TMPDIR</key>");
expect(plist).toContain(`<string>${tmpDir}</string>`);
});
it("shows actionable guidance when launchctl gui domain does not support bootstrap", async () => {
const originalPath = process.env.PATH;
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-launchctl-test-"));
try {
const binDir = path.join(tmpDir, "bin");
const homeDir = path.join(tmpDir, "home");
await fs.mkdir(binDir, { recursive: true });
await fs.mkdir(homeDir, { recursive: true });
const stubJsPath = path.join(binDir, "launchctl.js");
await fs.writeFile(
stubJsPath,
[
"const args = process.argv.slice(2);",
'if (args[0] === "bootstrap") {',
' process.stderr.write("Bootstrap failed: 125: Domain does not support specified action\\n");',
" process.exit(1);",
"}",
"process.exit(0);",
"",
].join("\n"),
"utf8",
);
if (process.platform === "win32") {
await fs.writeFile(
path.join(binDir, "launchctl.cmd"),
`@echo off\r\nnode "%~dp0\\launchctl.js" %*\r\n`,
"utf8",
);
} else {
const shPath = path.join(binDir, "launchctl");
await fs.writeFile(shPath, `#!/bin/sh\nnode "$(dirname "$0")/launchctl.js" "$@"\n`, "utf8");
await fs.chmod(shPath, 0o755);
}
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`;
const env: Record<string, string | undefined> = {
HOME: homeDir,
OPENCLAW_PROFILE: "default",
};
let message = "";
try {
await installLaunchAgent({
env,
stdout: new PassThrough(),
programArguments: ["node", "-e", "process.exit(0)"],
});
} catch (error) {
message = String(error);
}
expect(message).toContain("logged-in macOS GUI session");
expect(message).toContain("wrong user (including sudo)");
expect(message).toContain("https://docs.openclaw.ai/gateway");
} finally {
process.env.PATH = originalPath;
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
});
describe("resolveLaunchAgentPlistPath", () => {

View File

@@ -334,6 +334,14 @@ function isLaunchctlNotLoaded(res: { stdout: string; stderr: string; code: numbe
);
}
function isUnsupportedGuiDomain(detail: string): boolean {
const normalized = detail.toLowerCase();
return (
normalized.includes("domain does not support specified action") ||
normalized.includes("bootstrap failed: 125")
);
}
export async function stopLaunchAgent({
stdout,
env,
@@ -402,7 +410,19 @@ export async function installLaunchAgent({
await execLaunchctl(["enable", `${domain}/${label}`]);
const boot = await execLaunchctl(["bootstrap", domain, plistPath]);
if (boot.code !== 0) {
throw new Error(`launchctl bootstrap failed: ${boot.stderr || boot.stdout}`.trim());
const detail = (boot.stderr || boot.stdout).trim();
if (isUnsupportedGuiDomain(detail)) {
throw new Error(
[
`launchctl bootstrap failed: ${detail}`,
`LaunchAgent install requires a logged-in macOS GUI session for this user (${domain}).`,
"This usually means you are running from SSH/headless context or as the wrong user (including sudo).",
"Fix: sign in to the macOS desktop as the target user and rerun `openclaw gateway install --force`.",
"Headless deployments should use a dedicated logged-in user session or a custom LaunchDaemon (not shipped): https://docs.openclaw.ai/gateway",
].join("\n"),
);
}
throw new Error(`launchctl bootstrap failed: ${detail}`);
}
await execLaunchctl(["kickstart", "-k", `${domain}/${label}`]);