From a91c17c426f9bf5c0e201f0da99064e088e251fa Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 5 May 2026 05:13:59 +0100 Subject: [PATCH] fix: explain missing git during plugin install --- CHANGELOG.md | 1 + docs/channels/whatsapp.md | 10 ++++++++ docs/plugins/reference/whatsapp.md | 10 ++++++++ scripts/generate-plugin-inventory-doc.mjs | 17 +++++++++++++- src/cli/plugins-cli.install.test.ts | 28 +++++++++++++++++++++++ src/cli/plugins-command-helpers.ts | 26 ++++++++++++++++++--- 6 files changed, 88 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55b4cb6295d..a9006fcded1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ Docs: https://docs.openclaw.ai - Gateway/watch: suppress sync-I/O trace output during `pnpm gateway:watch --benchmark` unless explicitly requested, so CPU profiling no longer floods the terminal with stack traces. - Gateway/watch: when benchmark sync-I/O tracing is explicitly enabled, tee trace blocks to the benchmark output log and filter them from the terminal pane while keeping normal Gateway logs visible. - Plugins/runtime-deps: include `json5` in the memory-core plugin runtime dependency set so packaged `memory_search` sandboxes can resolve generated OpenClaw runtime chunks that parse JSON5 config. Fixes #77461. +- Plugins/Windows: show a Git install hint when npm plugin installation fails with `spawn git ENOENT`, and document the WhatsApp plugin's Git-on-PATH requirement for Baileys/libsignal installs. - Codex harness: preserve app-server usage-limit reset details and deliver OpenClaw-owned runtime failure notices through tool-only source-reply mode, so Telegram and other chat channels tell users when Codex subscription limits or API failures block a turn instead of going silent. (#77557) Thanks @pashpashpash. - Agents/OpenAI: default direct OpenAI Responses models to the SSE transport instead of WebSocket auto-selection, preventing pi runtime chat turns from hanging on servers where the WebSocket path stalls while the OpenAI HTTP stream works. Thanks @vincentkoc. - Plugins/update: repair missing plugin-local `openclaw` peer links before skipping unchanged npm plugin updates, so current external Codex installs can recover `openclaw/plugin-sdk/*` resolution during OTA repair. (#77544) Thanks @ProspectOre. diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md index 948157ae858..6e99c417589 100644 --- a/docs/channels/whatsapp.md +++ b/docs/channels/whatsapp.md @@ -26,6 +26,16 @@ openclaw plugins install @openclaw/whatsapp Use the bare package to follow the current official release tag. Pin an exact version only when you need a reproducible install. +On Windows, the WhatsApp plugin needs Git on `PATH` during npm install because +one of its Baileys/libsignal dependencies is fetched from a git URL. Install +Git for Windows, then restart the shell and rerun the install: + +```powershell +winget install --id Git.Git -e +``` + +Portable Git also works if its `bin` directory is on `PATH`. + Default DM policy is pairing for unknown senders. diff --git a/docs/plugins/reference/whatsapp.md b/docs/plugins/reference/whatsapp.md index c8d970c9d1f..b2bf8a43a62 100644 --- a/docs/plugins/reference/whatsapp.md +++ b/docs/plugins/reference/whatsapp.md @@ -18,6 +18,16 @@ Adds the WhatsApp channel surface for sending and receiving OpenClaw messages. channels: whatsapp +## Windows install note + +On Windows, the WhatsApp plugin needs Git on `PATH` during npm install because one of its Baileys/libsignal dependencies is fetched from a git URL. Install Git for Windows, then restart the shell and rerun the install: + +```powershell +winget install --id Git.Git -e +``` + +Portable Git also works if its `bin` directory is on `PATH`. + ## Related docs - [whatsapp](/channels/whatsapp) diff --git a/scripts/generate-plugin-inventory-doc.mjs b/scripts/generate-plugin-inventory-doc.mjs index b8cf018f574..80394d5909a 100644 --- a/scripts/generate-plugin-inventory-doc.mjs +++ b/scripts/generate-plugin-inventory-doc.mjs @@ -28,6 +28,20 @@ const PLUGIN_DOC_ALIASES = new Map([ ["tavily", "/tools/tavily"], ["tokenjuice", "/tools/tokenjuice"], ]); +const PLUGIN_REFERENCE_EXTRA_SECTIONS = new Map([ + [ + "whatsapp", + `## Windows install note + +On Windows, the WhatsApp plugin needs Git on \`PATH\` during npm install because one of its Baileys/libsignal dependencies is fetched from a git URL. Install Git for Windows, then restart the shell and rerun the install: + +\`\`\`powershell +winget install --id Git.Git -e +\`\`\` + +Portable Git also works if its \`bin\` directory is on \`PATH\`.`, + ], +]); function readJson(relativePath) { return JSON.parse(fs.readFileSync(path.join(ROOT, relativePath), "utf8")); @@ -376,6 +390,7 @@ ${record.docs.map((link) => `- ${docLink(link)}`).join("\n")}`; function renderReferencePage(record) { const relatedDocs = renderRelatedDocs(record); + const extraSections = PLUGIN_REFERENCE_EXTRA_SECTIONS.get(record.id); return `--- summary: "${record.description.replaceAll('"', '\\"')}" read_when: @@ -394,7 +409,7 @@ ${record.description} ## Surface -${record.surface}${relatedDocs ? `\n\n${relatedDocs}` : ""} +${record.surface}${extraSections ? `\n\n${extraSections}` : ""}${relatedDocs ? `\n\n${relatedDocs}` : ""} `; } diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index 6a0d53451ce..ed9b013fb37 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -1096,6 +1096,34 @@ describe("plugins cli install", () => { expect(runtimeErrors.at(-1)).toContain("npm install failed"); }); + it("adds a Git PATH hint when npm plugin dependency install cannot spawn git", async () => { + loadConfig.mockReturnValue({} as OpenClawConfig); + installPluginFromNpmSpec.mockResolvedValue({ + ok: false, + error: [ + "npm install failed:", + "npm error code ENOENT", + "npm error syscall spawn git", + "npm error path git", + ].join("\n"), + }); + installHooksFromNpmSpec.mockResolvedValue({ + ok: false, + error: "package.json missing openclaw.hooks", + }); + + await expect( + runPluginsCommand(["plugins", "install", "npm:@openclaw/whatsapp"]), + ).rejects.toThrow("__exit__:1"); + + expect(installPluginFromClawHub).not.toHaveBeenCalled(); + expect(runtimeErrors.at(-1)).toContain( + "one of this plugin's npm dependencies is fetched from a git URL", + ); + expect(runtimeErrors.at(-1)).toContain("winget install --id Git.Git -e"); + expect(runtimeErrors.at(-1)).toContain("Also not a valid hook pack"); + }); + it("does not resolve npm: prefixed bundled plugin ids through bundled installs", async () => { loadConfig.mockReturnValue({ plugins: { load: { paths: [] } } } as OpenClawConfig); installPluginFromNpmSpec.mockResolvedValue({ diff --git a/src/cli/plugins-command-helpers.ts b/src/cli/plugins-command-helpers.ts index c6cb7e0da8c..1b9efef9162 100644 --- a/src/cli/plugins-command-helpers.ts +++ b/src/cli/plugins-command-helpers.ts @@ -176,16 +176,36 @@ export function formatPluginInstallWithHookFallbackError( pluginError: string, hookError: string, ): string { + const formattedPluginError = formatPluginInstallAttemptError(pluginError); + const formattedHookError = formatPluginInstallAttemptError(hookError); if (/plugin already exists: .+ \(delete it first\)/.test(pluginError)) { - return `${pluginError}\nUse \`openclaw plugins update \` to upgrade the tracked plugin, or rerun install with \`--force\` to replace it.`; + return `${formattedPluginError}\nUse \`openclaw plugins update \` to upgrade the tracked plugin, or rerun install with \`--force\` to replace it.`; } if ( pluginError.startsWith("Invalid extensions directory:") || pluginError === "Invalid path: must stay within extensions directory" ) { - return pluginError; + return formattedPluginError; } - return `${pluginError}\nAlso not a valid hook pack: ${hookError}`; + return `${formattedPluginError}\nAlso not a valid hook pack: ${formattedHookError}`; +} + +const MISSING_GIT_FOR_NPM_DEPENDENCY_HINT = + "Git is required because one of this plugin's npm dependencies is fetched from a git URL, but `git` was not found on PATH. Install Git and rerun the install. On Windows, use `winget install --id Git.Git -e` or add a portable Git `bin` directory to PATH."; + +function formatPluginInstallAttemptError(error: string): string { + if (!isMissingGitForNpmDependencyError(error)) { + return error; + } + if (error.includes(MISSING_GIT_FOR_NPM_DEPENDENCY_HINT)) { + return error; + } + return `${error}\n\n${MISSING_GIT_FOR_NPM_DEPENDENCY_HINT}`; +} + +function isMissingGitForNpmDependencyError(error: string): boolean { + const normalized = normalizeLowercaseStringOrEmpty(error); + return /\bspawn\s+git\b/u.test(normalized) && /\benoent\b/u.test(normalized); } export function logHookPackRestartHint(runtime: RuntimeEnv = defaultRuntime) {