From fd008979f5794fda3d5f54151d36acd31d0de140 Mon Sep 17 00:00:00 2001 From: Nicky Leach Date: Fri, 30 Oct 2026 13:28:00 -0700 Subject: [PATCH] test: wait for the evidence expiry worker to settle (#18241) ## Thinking Path > - Paperclip coordinates AI work and retains execution evidence. > - Evidence retention uses an asynchronous worker and a shared scheduler. > - The retention test checked the next deadline as soon as an archive disappeared. > - File removal can finish before the worker publishes that deadline. > - This pull request waits for worker settlement before asserting its next wake. > - The test keeps its deletion, busy-session preservation, and quiet-worker checks. ## Linked Issues or Issue Description **What happened?** CI for #18239 failed in `native-harness-backup-expiration.test.ts`. The healthy-expiry test observed the file removal while `nextWakeAt()` was still null. Subtracting the current time then produced a large negative number. **Expected behavior** The retention test should check the deadline after the asynchronous worker has settled. **Steps to reproduce** Run the server test shard with a slow filesystem completion. The deletion predicate can become true before scheduler publication finishes. ## What Changed - Keep the existing file-removal wait open until tracked work is idle and the expected busy-session retry deadline is registered. - Keep the held archive and quiet-worker assertions. ## Verification - Original CI failure: [server shard 3/12](https://github.com/paperclipai/paperclip/actions/runs/40976031169/job/123804262266). - The targeted test passes with its real PostgreSQL fixture: `node node_modules/vitest/vitest.mjs run --project @paperclipai/server server/src/__tests__/native-harness-backup-expiration.test.ts -t 'lets healthy saved expiry preempt' --maxWorkers 1`. - After rebasing onto master, the focused retention test passes again with its real PostgreSQL fixture. The PR now changes one test, with seven insertions and two deletions. - Master independently fixed the authentication timeout and GitHub-recovery checkpoint race. Their redundant commits were dropped during rebase. - [Full GitHub CI](https://github.com/paperclipai/paperclip/actions/runs/40984842413) passes on `bbaa7c3c384f6c220ce2c979f0e9b5d01ddb1f77`: all 48 jobs succeeded. Standard Greptile is 5/5 on that head, and GitHub reports the PR clean. - Broader coverage runs in GitHub CI. No local full build or full test suite was run for this assertion-only change. ## Risks Low risk. The retention test waits for the condition it already asserts. The existing deletion, busy-session preservation, future-deadline, and quiet-worker checks remain in place. No production code or time budget changes. ## Model Used OpenAI GPT-6 through Codex, with repository inspection, terminal tools, and code execution. The serving variant and context window size are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip --- .../__tests__/native-harness-backup-expiration.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/native-harness-backup-expiration.test.ts b/server/src/__tests__/native-harness-backup-expiration.test.ts index b24c12a80c9..2437d9711b5 100644 --- a/server/src/__tests__/native-harness-backup-expiration.test.ts +++ b/server/src/__tests__/native-harness-backup-expiration.test.ts @@ -645,8 +645,13 @@ describeEmbeddedPostgres("native harness backup expiration", () => { const available = await seedArchive(healthy.root, { name: `${dueAt - 7 * DAY_MS - 1}-${randomUUID()}` }); await startEvidence({ isSessionBusy: scope => scope === busy.scope }); await settled(dueAt); await vi.advanceTimersByTimeAsync(dueAt - Date.now()); - await vi.waitFor(async () => { await vi.advanceTimersByTimeAsync(100); expect(await exists(available)).toBe(false); }); - expect(await exists(held)).toBe(true); expect(coordinator!.nextWakeAt()! - Date.now()).toBeGreaterThan(59 * 60_000); + await vi.waitFor(async () => { + await vi.advanceTimersByTimeAsync(100); + expect(await exists(available)).toBe(false); + expect(idleWorkSnapshot().active).toBe(0); + expect(coordinator!.nextWakeAt()! - Date.now()).toBeGreaterThan(59 * 60_000); + }); + expect(await exists(held)).toBe(true); await quiet(60_000); });