mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix: advance stale schedule nextRunAt on daemon restart
On restart, persisted nextRunAt could be in the past, showing stale dates in `schedule ls`. Now recoverInterruptedRuns() advances any past-due nextRunAt forward to the next future tick. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -171,6 +171,47 @@ describe("ScheduleService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("advances stale nextRunAt on daemon restart", async () => {
|
||||
const service1 = new ScheduleService({
|
||||
paseoHome: tempDir,
|
||||
logger: createTestLogger(),
|
||||
agentManager: new AgentManager({ logger: createTestLogger() }),
|
||||
agentStorage,
|
||||
now: () => now,
|
||||
runner: async () => ({ agentId: null, output: "ok" }),
|
||||
});
|
||||
|
||||
const created = await service1.create({
|
||||
prompt: "Periodic check",
|
||||
cadence: { type: "every", everyMs: 60_000 },
|
||||
target: {
|
||||
type: "new-agent",
|
||||
config: { provider: "claude", cwd: tempDir },
|
||||
},
|
||||
});
|
||||
|
||||
expect(created.nextRunAt).toBe("2026-01-01T00:01:00.000Z");
|
||||
await service1.stop();
|
||||
|
||||
// Simulate daemon restart 10 minutes later
|
||||
now = new Date("2026-01-01T00:10:00.000Z");
|
||||
const service2 = new ScheduleService({
|
||||
paseoHome: tempDir,
|
||||
logger: createTestLogger(),
|
||||
agentManager: new AgentManager({ logger: createTestLogger() }),
|
||||
agentStorage,
|
||||
now: () => now,
|
||||
runner: async () => ({ agentId: null, output: "ok" }),
|
||||
});
|
||||
await service2.start();
|
||||
|
||||
const inspected = await service2.inspect(created.id);
|
||||
expect(
|
||||
new Date(inspected.nextRunAt!).getTime(),
|
||||
).toBeGreaterThan(now.getTime());
|
||||
await service2.stop();
|
||||
});
|
||||
|
||||
test("keeps schedules paused when an in-flight run finishes after pause", async () => {
|
||||
let releaseRun: (() => void) | null = null;
|
||||
const runStarted = new Promise<void>((resolve) => {
|
||||
|
||||
@@ -251,23 +251,46 @@ export class ScheduleService {
|
||||
const schedules = await this.store.list();
|
||||
const now = this.now();
|
||||
for (const schedule of schedules) {
|
||||
const runningIndex = schedule.runs.findIndex((run) => run.status === "running");
|
||||
if (runningIndex === -1) {
|
||||
continue;
|
||||
let updated = { ...schedule };
|
||||
let dirty = false;
|
||||
|
||||
// Mark any in-flight runs as failed
|
||||
const runningIndex = updated.runs.findIndex(
|
||||
(run) => run.status === "running",
|
||||
);
|
||||
if (runningIndex !== -1) {
|
||||
const runs = [...updated.runs];
|
||||
runs[runningIndex] = {
|
||||
...runs[runningIndex],
|
||||
status: "failed",
|
||||
endedAt: now.toISOString(),
|
||||
error: "Daemon restarted before the scheduled run completed",
|
||||
};
|
||||
updated = { ...updated, runs };
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// Advance stale nextRunAt for active schedules
|
||||
if (
|
||||
updated.status === "active" &&
|
||||
updated.nextRunAt &&
|
||||
new Date(updated.nextRunAt).getTime() <= now.getTime()
|
||||
) {
|
||||
let nextRunAt = computeNextRunAt(
|
||||
updated.cadence,
|
||||
new Date(updated.nextRunAt),
|
||||
);
|
||||
while (nextRunAt.getTime() <= now.getTime()) {
|
||||
nextRunAt = computeNextRunAt(updated.cadence, nextRunAt);
|
||||
}
|
||||
updated = { ...updated, nextRunAt: nextRunAt.toISOString() };
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
updated = { ...updated, updatedAt: now.toISOString() };
|
||||
await this.store.put(updated);
|
||||
}
|
||||
const runs = [...schedule.runs];
|
||||
runs[runningIndex] = {
|
||||
...runs[runningIndex],
|
||||
status: "failed",
|
||||
endedAt: now.toISOString(),
|
||||
error: "Daemon restarted before the scheduled run completed",
|
||||
};
|
||||
const nextSchedule = {
|
||||
...schedule,
|
||||
runs,
|
||||
updatedAt: now.toISOString(),
|
||||
};
|
||||
await this.store.put(nextSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user