Skip to content

v0.4.4 — the queue actually runs again

Latest

Choose a tag to compare

@davorinpavlica davorinpavlica released this 03 Aug 08:07

v0.4.3 did not run queued tasks. A task queued for the 00:10 window sat PENDING for 13 hours across two windows. If you are on v0.4.3, upgrade.

The daemon never asked when the task was due

The wait loop released a batch only once NOW >= anchor + 120s, so for a 00:10 reset it would act at 00:12. But the anchor is replaced with the next reset as soon as the stored one is past, which happened at 00:11. From 00:12 the daemon saw "next reset 05:10, keep waiting" and never released anything.

The trigger window was two minutes wide. The anchor closed it after one.

A task already stores the reset it was scheduled for in RUN_AFTER, and that fact does not depend on the anchor. The daemon now releases when either the anchor's window has opened or a task's own RUN_AFTER is past. That is the load-bearing guarantee: any future anchor defect can now only make the queue late, never make it skip a window.

The 2026-08-01 runaway was never fixed, only reshaped

The 30-minute drift threshold holds a later reset_at and then adopts it once the gap crosses 31 minutes. Holding does nothing to stop the projection advancing, so the gap re-crosses every 31 minutes and the anchor steps forward with the clock. Measured: 10:20, 10:51, 11:22, 11:53, 12:24. Net drift identical to the original runaway, only the shape changed, from a ramp to a staircase — which is why 30-minute log samples made it look stable.

It stopped only when a task ran and gave the session real usage. That is the discriminator a gap cannot provide: a session with usage reports a commitment that holds still, an idle session reports a projection that slides. Over the threshold, utilization now decides. Absent is not zero, so the cookie and API paths keep the old behaviour rather than freezing.

The tests that should have caught it

423 tests were green through both failures. Every suite cut daemon.sh at # ── Main loop and tested only the functions above it, so the code deciding when a task runs had no test at all — and every timing test was a snapshot of one moment, which cannot show drift.

  • tests/test-e2e-window.sh runs the real main loop against a simulated clock and a fake claude. Sleeps advance simulated time instead of waiting, so a five-hour window passes in milliseconds. Verified against the pre-fix commit: the section replaying the production failure fails there and passes here.
  • tests/test-time-series.sh calls the timing functions over hundreds of consecutive polls with the clock moving, asserting on the sequence rather than a value.
  • scripts/release-check.sh and a pre-push hook block a tag push unless every suite is green, with the end-to-end suite required rather than merely included — a missing e2e file fails the gate instead of quietly passing. This release was the first to go through it.

463 assertions across 11 suites, up from 423 across 8.

Also

docs/RESET-TIME.md claimed the drift threshold was "correct under either reading". That was never measured and was false, and being read as settled is why the anchor was ruled out as a suspect for six hours. It now carries both failures with their measurements, and a rule: a claim without the measurement supporting it is unverified, whatever it asserts.

Upgrading

Run ./install.sh. It links release-check.sh and installs the release gate. No data formats changed and existing queued tasks keep working.