mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:00:42 +00:00
fix: warn on legacy WhatsApp cron health checks
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user