fix: explain missing git during plugin install

This commit is contained in:
Peter Steinberger
2026-05-05 05:13:59 +01:00
parent cf3ce08b91
commit a91c17c426
6 changed files with 88 additions and 4 deletions

View File

@@ -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.

View File

@@ -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`.
<CardGroup cols={3}>
<Card title="Pairing" icon="link" href="/channels/pairing">
Default DM policy is pairing for unknown senders.

View File

@@ -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)

View File

@@ -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}` : ""}
`;
}

View File

@@ -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({

View File

@@ -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 <id-or-npm-spec>\` to upgrade the tracked plugin, or rerun install with \`--force\` to replace it.`;
return `${formattedPluginError}\nUse \`openclaw plugins update <id-or-npm-spec>\` 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) {