Cron: clean run-log write queue entries (#23968)

* Cron: clean run-log write queue entries

* Changelog: add cron run-log write-queue cleanup note
This commit is contained in:
Tak Hoffman
2026-02-22 17:16:42 -06:00
committed by GitHub
parent 22c9018303
commit 9bc265f379
3 changed files with 32 additions and 2 deletions

View File

@@ -71,6 +71,7 @@ Docs: https://docs.openclaw.ai
- Cron/Schedule: for `every` jobs, prefer `lastRunAtMs + everyMs` when still in the future after restarts, then fall back to anchor scheduling for catch-up windows, so NEXT timing matches the last successful cadence. (#22895) Thanks @SidQin-cyber.
- Cron/Service: execute manual `cron.run` jobs outside the cron lock (while still persisting started/finished state atomically) so `cron.list` and `cron.status` remain responsive during long forced runs. (#23628) Thanks @dsgraves.
- Cron/Timer: keep a watchdog recheck timer armed while `onTimer` is actively executing so the scheduler continues polling even if a due-run tick stalls for an extended period. (#23628) Thanks @dsgraves.
- Cron/Run log: clean up settled per-path run-log write queue entries so long-running cron uptime does not retain stale promise bookkeeping in memory.
- Cron/Isolation: force fresh session IDs for isolated cron runs so `sessionTarget="isolated"` executions never reuse prior run context. (#23470) Thanks @echoVic.
- Plugins/Install: strip `workspace:*` devDependency entries from copied plugin manifests before `npm install --omit=dev`, preventing `EUNSUPPORTEDPROTOCOL` install failures for npm-published channel plugins (including Feishu and MS Teams).
- Feishu/Plugins: restore bundled Feishu SDK availability for global installs and strip `openclaw: workspace:*` from plugin `devDependencies` during plugin-version sync so npm-installed Feishu plugins do not fail dependency install. (#23611, #23645, #23603)

View File

@@ -2,7 +2,12 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { appendCronRunLog, readCronRunLogEntries, resolveCronRunLogPath } from "./run-log.js";
import {
appendCronRunLog,
getPendingCronRunLogWriteCountForTests,
readCronRunLogEntries,
resolveCronRunLogPath,
} from "./run-log.js";
describe("cron run log", () => {
async function withRunLogDir(prefix: string, run: (dir: string) => Promise<void>) {
@@ -185,4 +190,18 @@ describe("cron run log", () => {
expect(entries[1]?.usage?.input_tokens).toBeUndefined();
});
});
it("cleans up pending-write bookkeeping after appends complete", async () => {
await withRunLogDir("openclaw-cron-log-pending-", async (dir) => {
const logPath = path.join(dir, "runs", "job-cleanup.jsonl");
await appendCronRunLog(logPath, {
ts: 1,
jobId: "job-cleanup",
action: "finished",
status: "ok",
});
expect(getPendingCronRunLogWriteCountForTests()).toBe(0);
});
});
});

View File

@@ -27,6 +27,10 @@ export function resolveCronRunLogPath(params: { storePath: string; jobId: string
const writesByPath = new Map<string, Promise<void>>();
export function getPendingCronRunLogWriteCountForTests() {
return writesByPath.size;
}
async function pruneIfNeeded(filePath: string, opts: { maxBytes: number; keepLines: number }) {
const stat = await fs.stat(filePath).catch(() => null);
if (!stat || stat.size <= opts.maxBytes) {
@@ -63,7 +67,13 @@ export async function appendCronRunLog(
});
});
writesByPath.set(resolved, next);
await next;
try {
await next;
} finally {
if (writesByPath.get(resolved) === next) {
writesByPath.delete(resolved);
}
}
}
export async function readCronRunLogEntries(