mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(cron): treat zero sessionRetention as disabled instead of pruning all run sessions (#120213)
This commit is contained in:
@@ -799,7 +799,7 @@ Disable automations: `cron.enabled: false` or `OPENCLAW_SKIP_CRON=1`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Maintenance">
|
||||
`cron.sessionRetention` (default `24h`, `false` disables) prunes isolated run-session entries. Run history keeps the newest 2000 terminal rows per job; lost rows retain their 24-hour cleanup window.
|
||||
`cron.sessionRetention` (default `24h`, `false` or `"0h"` disables) prunes isolated run-session entries. Run history keeps the newest 2000 terminal rows per job; lost rows retain their 24-hour cleanup window.
|
||||
</Accordion>
|
||||
<Accordion title="Legacy store migration">
|
||||
On upgrade, run `openclaw doctor --fix` to import historical `~/.openclaw/cron/jobs.json`, `jobs-state.json`, `jobs-quarantine.json`, and `runs/*.jsonl` files into SQLite and archive the originals with a `.migrated` suffix. Malformed job rows remain recoverable in SQLite while valid jobs keep running.
|
||||
|
||||
+1
-1
@@ -216,7 +216,7 @@ The scheduler does not classify final-output prose or approval-looking refusal p
|
||||
|
||||
Retention behavior:
|
||||
|
||||
- `cron.sessionRetention` (default `24h`, or `false` to disable) prunes completed isolated run sessions.
|
||||
- `cron.sessionRetention` (default `24h`, or `false` to disable; a zero duration such as `"0h"` also disables) prunes completed isolated run sessions.
|
||||
- Run history keeps the newest 2000 terminal rows per job. Lost rows retain the standard 24-hour lost-task cleanup window.
|
||||
|
||||
## Migrating older jobs
|
||||
|
||||
@@ -1470,14 +1470,14 @@ Current builds no longer include the TCP bridge. Nodes connect over the Gateway
|
||||
webhookSsrfPolicy: {
|
||||
allowedHostnames: ["127.0.0.1"], // optional exact exception for a trusted receiver
|
||||
},
|
||||
sessionRetention: "24h", // duration string or false
|
||||
sessionRetention: "24h", // duration string ("0h" disables) or false
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- `enabled`: execute stored automation jobs (default: `true`). Set `false` to pause all automation execution without deleting jobs.
|
||||
- `triggers.enabled`: also run event-driven automation triggers (default: `false`).
|
||||
- `sessionRetention`: how long to keep completed isolated automation run sessions before pruning SQLite session rows. Also controls cleanup of archived deleted automation transcripts. Default: `24h`; set `false` to disable.
|
||||
- `sessionRetention`: how long to keep completed isolated automation run sessions before pruning SQLite session rows. Also controls cleanup of archived deleted automation transcripts. Default: `24h`; set `false` or a zero duration such as `"0h"` to disable (negative durations are invalid).
|
||||
- Run history automatically keeps the newest 2000 terminal rows per job. Lost rows retain their 24-hour cleanup window.
|
||||
- `webhookToken`: bearer token used for automation webhook POST delivery (`delivery.mode = "webhook"`), if omitted no auth header is sent.
|
||||
- `webhookSsrfPolicy`: shared outbound SSRF policy for primary, completion, failure-destination, and failure-alert webhooks. Private/internal targets are blocked when omitted. Prefer exact `allowedHostnames`; use `dangerouslyAllowPrivateNetwork: true` only for trusted private-network receivers. The narrow fake-IP proxy flags are `allowRfc2544BenchmarkRange` and `allowIpv6UniqueLocalRange`.
|
||||
|
||||
@@ -414,7 +414,7 @@ candidate contains a redacted secret placeholder such as `***` or `[redacted]`.
|
||||
}
|
||||
```
|
||||
|
||||
- `sessionRetention`: prune completed isolated run sessions from SQLite session rows (default `24h`; set `false` to disable).
|
||||
- `sessionRetention`: prune completed isolated run sessions from SQLite session rows (default `24h`; set `false` or a zero duration such as `"0h"` to disable).
|
||||
- Run history automatically keeps the newest 2000 terminal rows per job; lost rows retain their 24-hour cleanup window.
|
||||
- See [Cron jobs](/automation/cron-jobs) for feature overview and CLI examples.
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ artifacts before importing.
|
||||
|
||||
Isolated cron runs create their own session entries/transcripts with dedicated retention:
|
||||
|
||||
- `cron.sessionRetention` (default `"24h"`) prunes old isolated cron run sessions from the store; `false` disables.
|
||||
- `cron.sessionRetention` (default `"24h"`) prunes old isolated cron run sessions from the store; `false` or a zero duration such as `"0h"` disables.
|
||||
- Run history keeps the newest 2000 terminal rows per cron job. Lost rows retain their 24-hour cleanup window.
|
||||
|
||||
When cron force-creates a new isolated run session, it sanitizes the previous `cron:<jobId>` session entry before writing the new row: it carries safe preferences (thinking/fast/verbose/reasoning settings, labels, display name) and explicit user-selected model/auth overrides, but drops ambient conversation context (channel/group routing, send/queue policy, elevation, origin, ACP runtime binding) so a fresh isolated run cannot inherit stale delivery or runtime authority from an older run.
|
||||
|
||||
@@ -100,7 +100,7 @@ export const AUTOMATION_FIELD_HELP: Record<string, string> = {
|
||||
"cron.webhookSsrfPolicy.allowIpv6UniqueLocalRange":
|
||||
"Allows automation webhooks to IPv6 Unique Local Addresses (fc00::/7). Use only with trusted fake-IP proxy environments.",
|
||||
"cron.sessionRetention":
|
||||
"Controls how long completed automation run sessions are kept before pruning (`24h`, `7d`, `1h30m`, or `false` to disable pruning; default: `24h`). Use shorter retention to reduce storage growth on high-frequency schedules.",
|
||||
"Controls how long completed automation run sessions are kept before pruning (`24h`, `7d`, `1h30m`, or `false` to disable pruning; a zero duration such as `0h` also disables; default: `24h`). Use shorter retention to reduce storage growth on high-frequency schedules.",
|
||||
transcripts:
|
||||
"Core transcript capture settings for meeting notes, recording-capable agent tools, and configured live meeting auto-start sources. Meeting plugins capture durable notes by default; set enabled to false to opt out globally.",
|
||||
"transcripts.enabled":
|
||||
|
||||
@@ -32,6 +32,7 @@ export type CronConfig = {
|
||||
/**
|
||||
* How long to retain completed cron run sessions before automatic pruning.
|
||||
* Accepts a duration string (e.g. "24h", "7d", "1h30m") or `false` to disable pruning.
|
||||
* A zero duration (e.g. "0h") also disables pruning; negative durations are invalid.
|
||||
* Default: "24h".
|
||||
*/
|
||||
sessionRetention?: string | false;
|
||||
|
||||
@@ -527,6 +527,32 @@ describe("sweepCronRunSessions", () => {
|
||||
expect(result.pruned).toBe(0);
|
||||
});
|
||||
|
||||
it.each([["0h"], ["0s"], ["0"]])(
|
||||
"treats a zero retention (%s) as disabled instead of pruning everything",
|
||||
async (sessionRetention) => {
|
||||
const now = Date.now();
|
||||
const store: Record<string, SessionEntry> = {
|
||||
"agent:main:cron:job1:run:run1": {
|
||||
sessionId: "run1",
|
||||
updatedAt: now - 100 * 3_600_000,
|
||||
},
|
||||
};
|
||||
await seedSessionEntries(storePath, store);
|
||||
|
||||
const result = await sweepCronRunSessions({
|
||||
cronConfig: { sessionRetention },
|
||||
sessionStorePath: storePath,
|
||||
nowMs: now,
|
||||
log,
|
||||
force: true,
|
||||
});
|
||||
|
||||
expect(result.swept).toBe(false);
|
||||
expect(result.pruned).toBe(0);
|
||||
expect(readSessionEntries(storePath)).toHaveProperty("agent:main:cron:job1:run:run1");
|
||||
},
|
||||
);
|
||||
|
||||
it("sweeps immediately when disabled retention is enabled again", async () => {
|
||||
const now = Date.now();
|
||||
const sessionKey = "agent:main:cron:job1:run:expired-run";
|
||||
|
||||
@@ -33,7 +33,15 @@ function resolveRetentionMs(cronConfig?: CronConfig): number | null {
|
||||
const raw = cronConfig?.sessionRetention;
|
||||
if (typeof raw === "string" && raw.trim()) {
|
||||
try {
|
||||
return parseDurationMs(raw.trim(), { defaultUnit: "h" });
|
||||
const ms = parseDurationMs(raw.trim(), { defaultUnit: "h" });
|
||||
// A zero retention ("0h") is a disable signal, not "prune everything":
|
||||
// cutoff would equal now and the next sweep would delete every cron run
|
||||
// session. Negative durations never get here (the parser rejects them);
|
||||
// the <= 0 check stays defensive.
|
||||
if (ms <= 0) {
|
||||
return null;
|
||||
}
|
||||
return ms;
|
||||
} catch {
|
||||
return DEFAULT_RETENTION_MS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user