fix: warn on legacy WhatsApp cron health checks

This commit is contained in:
Peter Steinberger
2026-05-01 22:45:47 +01:00
parent 5657710e15
commit 6af6688ce2
7 changed files with 119 additions and 2 deletions

View File

@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Doctor/WhatsApp: warn when Linux crontabs still run the legacy `ensure-whatsapp.sh` health check, which can misreport `Gateway inactive` when cron lacks the systemd user-bus environment. Fixes #60204. Thanks @mySebbe.
- Slack/setup: print the generated app manifest as plain JSON instead of embedding it inside the framed setup note, so it can be copied into Slack without deleting border characters. Fixes #65751. Thanks @theDanielJLewis.
- Channels/WhatsApp: route CLI logout through the live Gateway and stop runtime-backed listeners before channel removal, so removing a WhatsApp account does not leave the old socket replying until restart. Fixes #67746. Thanks @123Mismail.
- Agents/Codex: stop prompting message-tool-only source turns to finish with `NO_REPLY`, so quiet turns are represented by not calling the visible message tool instead of conflicting final-text instructions. Thanks @pashpashpash.

View File

@@ -553,6 +553,14 @@ Behavior notes:
openclaw logs --follow
```
If `~/.openclaw/logs/whatsapp-health.log` says `Gateway inactive` but
`openclaw gateway status` and `openclaw channels status --probe` show the
gateway and WhatsApp are healthy, run `openclaw doctor`. On Linux, doctor
warns about legacy crontab entries that still invoke
`~/.openclaw/bin/ensure-whatsapp.sh`; remove those stale entries with
`crontab -e` because cron can lack the systemd user-bus environment and
make that old script misreport gateway health.
If needed, re-link with `channels login`.
</Accordion>

View File

@@ -44,6 +44,7 @@ Notes:
- `doctor --fix --non-interactive` reports missing or stale gateway service definitions but does not install or rewrite them outside update repair mode. Run `openclaw gateway install` for a missing service, or `openclaw gateway install --force` when you intentionally want to replace the launcher.
- State integrity checks now detect orphan transcript files in the sessions directory. Archiving them as `.deleted.<timestamp>` requires an interactive confirmation; `--fix`, `--yes`, and headless runs leave them in place.
- Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime.
- On Linux, doctor warns when the user's crontab still runs legacy `~/.openclaw/bin/ensure-whatsapp.sh`; that script is no longer maintained and can log false WhatsApp gateway outages when cron lacks the systemd user-bus environment.
- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions. It also repairs missing configured downloadable plugins when the registry can resolve them.
- Doctor repairs stale plugin config by removing missing plugin ids from `plugins.allow`/`plugins.entries`, plus matching dangling channel config, heartbeat targets, and channel model overrides when plugin discovery is healthy.
- Doctor quarantines invalid plugin config by disabling the affected `plugins.entries.<id>` entry and removing its invalid `config` payload. Gateway startup already skips only that bad plugin so other plugins and channels can keep running.

View File

@@ -298,6 +298,8 @@ That stages grounded durable candidates into the short-term dreaming store while
Doctor only auto-migrates `notify: true` jobs when it can do so without changing behavior. If a job combines legacy notify fallback with an existing non-webhook delivery mode, doctor warns and leaves that job for manual review.
On Linux, doctor also warns when the user's crontab still invokes legacy `~/.openclaw/bin/ensure-whatsapp.sh`. That host-local script is not maintained by current OpenClaw and can write false `Gateway inactive` messages to `~/.openclaw/logs/whatsapp-health.log` when cron cannot reach the systemd user bus. Remove the stale crontab entry with `crontab -e`; use `openclaw channels status --probe`, `openclaw doctor`, and `openclaw gateway status` for current health checks.
</Accordion>
<Accordion title="3c. Session lock cleanup">
Doctor scans every agent session directory for stale write-lock files — files left behind when a session exited abnormally. For each lock file found it reports: the path, PID, whether the PID is still alive, lock age, and whether it is considered stale (dead PID or older than 30 minutes). In `--fix` / `--repair` mode it removes stale lock files automatically; otherwise it prints a note and instructs you to rerun with `--fix`.

View File

@@ -3,7 +3,7 @@ import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { maybeRepairLegacyCronStore } from "./doctor-cron.js";
import { maybeRepairLegacyCronStore, noteLegacyWhatsAppCrontabHealthCheck } from "./doctor-cron.js";
type TerminalNote = (message: string, title?: string) => void;
@@ -385,3 +385,46 @@ describe("maybeRepairLegacyCronStore", () => {
);
});
});
describe("noteLegacyWhatsAppCrontabHealthCheck", () => {
it("warns about legacy ensure-whatsapp crontab entries on Linux", async () => {
await noteLegacyWhatsAppCrontabHealthCheck({
platform: "linux",
readCrontab: async () => ({
stdout: [
"# keep comments ignored",
"*/5 * * * * ~/.openclaw/bin/ensure-whatsapp.sh >> ~/.openclaw/logs/whatsapp-health.log 2>&1",
"0 9 * * * /usr/bin/true",
"",
].join("\n"),
}),
});
expect(noteMock).toHaveBeenCalledWith(
expect.stringContaining("Legacy WhatsApp crontab health check detected"),
"Cron",
);
expect(noteMock).toHaveBeenCalledWith(
expect.stringContaining("systemd user bus environment is missing"),
"Cron",
);
expect(noteMock).toHaveBeenCalledWith(expect.stringContaining("Matched 1 entry"), "Cron");
});
it("ignores missing crontab support and non-Linux hosts", async () => {
await noteLegacyWhatsAppCrontabHealthCheck({
platform: "darwin",
readCrontab: async () => {
throw new Error("should not read crontab on non-Linux");
},
});
await noteLegacyWhatsAppCrontabHealthCheck({
platform: "linux",
readCrontab: async () => {
throw Object.assign(new Error("crontab missing"), { code: "ENOENT" });
},
});
expect(noteMock).not.toHaveBeenCalled();
});
});

View File

@@ -1,3 +1,5 @@
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { formatCliCommand } from "../cli/command-format.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveCronStorePath, loadCronStore, saveCronStore } from "../cron/store.js";
@@ -20,6 +22,12 @@ type CronDoctorOutcome = {
warnings: string[];
};
type CrontabReader = () => Promise<{ stdout: string; stderr?: string }>;
const execFileAsync = promisify(execFile);
const LEGACY_WHATSAPP_HEALTH_SCRIPT_RE =
/(?:^|\s)(?:"[^"]*ensure-whatsapp\.sh"|'[^']*ensure-whatsapp\.sh'|[^\s#;|&]*ensure-whatsapp\.sh)\b/u;
function pluralize(count: number, noun: string) {
return `${count} ${noun}${count === 1 ? "" : "s"}`;
}
@@ -129,6 +137,58 @@ function migrateLegacyNotifyFallback(params: {
return { changed, warnings };
}
async function readUserCrontab(): Promise<{ stdout: string; stderr?: string }> {
const result = await execFileAsync("crontab", ["-l"], {
encoding: "utf8",
windowsHide: true,
});
return {
stdout: result.stdout,
stderr: result.stderr,
};
}
function findLegacyWhatsAppHealthCrontabLines(crontab: string): string[] {
return crontab
.split(/\r?\n/u)
.map((line) => line.trim())
.filter((line) => line.length > 0 && !line.startsWith("#"))
.filter((line) => LEGACY_WHATSAPP_HEALTH_SCRIPT_RE.test(line));
}
export async function noteLegacyWhatsAppCrontabHealthCheck(
params: {
platform?: NodeJS.Platform;
readCrontab?: CrontabReader;
} = {},
): Promise<void> {
if ((params.platform ?? process.platform) !== "linux") {
return;
}
let crontab: string;
try {
crontab = (await (params.readCrontab ?? readUserCrontab)()).stdout;
} catch {
return;
}
const legacyLines = findLegacyWhatsAppHealthCrontabLines(crontab);
if (legacyLines.length === 0) {
return;
}
note(
[
"Legacy WhatsApp crontab health check detected.",
"`~/.openclaw/bin/ensure-whatsapp.sh` is not maintained by current OpenClaw and can misreport `Gateway inactive` from cron when the systemd user bus environment is missing.",
`Remove the stale crontab entry with ${formatCliCommand("crontab -e")}; use ${formatCliCommand("openclaw channels status --probe")}, ${formatCliCommand("openclaw doctor")}, and ${formatCliCommand("openclaw gateway status")} for current health checks.`,
`Matched ${pluralize(legacyLines.length, "entry")}.`,
].join("\n"),
"Cron",
);
}
export async function maybeRepairLegacyCronStore(params: {
cfg: OpenClawConfig;
options: DoctorOptions;

View File

@@ -284,7 +284,9 @@ async function runSessionTranscriptsHealth(ctx: DoctorHealthFlowContext): Promis
}
async function runLegacyCronHealth(ctx: DoctorHealthFlowContext): Promise<void> {
const { maybeRepairLegacyCronStore } = await import("../commands/doctor-cron.js");
const { maybeRepairLegacyCronStore, noteLegacyWhatsAppCrontabHealthCheck } =
await import("../commands/doctor-cron.js");
await noteLegacyWhatsAppCrontabHealthCheck();
await maybeRepairLegacyCronStore({
cfg: ctx.cfg,
options: ctx.options,