Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .docker/app/runtime-deps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"packageManager": "pnpm@10.29.3",
"dependencies": {
"dompurify": "3.4.16",
"dompurify": "3.4.17",
"jsdom": "26.1.0",
"snowflake-sdk": "3.3.0",
"zod": "3.25.76"
Expand All @@ -14,7 +14,7 @@
"axios": "1.20.0",
"follow-redirects": "1.16.1",
"moment": "2.31.0",
"toml": "4.2.0"
"toml": "5.0.1"
}
}
}
20 changes: 10 additions & 10 deletions .docker/app/runtime-deps/api/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

This file tracks product releases for Roomote (single monorepo version). Automated release entries are prepended by `pnpm run version`.

## 1.19.3 (2026-10-24)

Roomote 1.19.3 keeps scheduled automations and worker health checks running reliably while memory history backfills make progress in the background.

### Highlights

- Keep memory history backfills from crowding out scheduled automations and worker health checks.
- Automatically repair outdated scheduler offsets so heartbeat and automation checks return to their intended cadence.

### Patch changes

- Memory history backfills no longer pile up overlapping collector runs that delay scheduled automations, make worker health checks go stale, and trigger repeated worker restarts. Collection runs one at a time and yields between bounded backfill passes, preserving progress for the next scheduled run.
- Scheduled heartbeat and automation checks now recover their intended cadence after an interval change instead of continuing to run late because of an outdated scheduler offset. Roomote repairs affected schedules automatically when the background worker starts.

## 1.19.2 (2026-10-22)

Roomote 1.19.2 lets you continue tasks saved before an upgrade without losing access to their saved work.
Expand Down
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"@sentry/node": "10.45.0",
"@trpc/client": "^11.15.0",
"@trpc/server": "^11.15.0",
"dompurify": "3.4.16",
"dompurify": "3.4.17",
"hono": "4.13.11",
"jose": "^6.2.3",
"jsdom": "26.1.0",
Expand Down
74 changes: 74 additions & 0 deletions apps/bullmq/src/job-scheduler-repair.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions apps/bullmq/src/job-scheduler-repair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { Queue } from 'bullmq';

const SCHEDULER_PAGE_SIZE = 100;

/**
* bullmq keeps an `every` scheduler's stored offset when its interval changes,
* and adds that whole offset when it catches up on missed iterations. A
* scheduler that once ran hourly and now runs every minute therefore keeps
* firing about once an hour: the heartbeat goes stale between runs and
* per-minute work runs late.
*
* Removes every scheduler whose offset is not smaller than its interval so the
* upserts that follow re-create it with a fresh offset. Returns the removed
* scheduler keys.
*/
export async function removeSchedulersWithStaleOffsets(
queue: Pick<Queue, 'getJobSchedulers' | 'removeJobScheduler'>,
): Promise<string[]> {
// Collect first: schedulers are paged by sorted-set rank, so deleting while
// paging would shift later entries left and skip them.
const stale: string[] = [];

for (let start = 0; ; start += SCHEDULER_PAGE_SIZE) {
const page = await queue.getJobSchedulers(
start,
start + SCHEDULER_PAGE_SIZE - 1,
true,
);

for (const scheduler of page) {
const every = Number(scheduler.every);
const offset = Number(scheduler.offset);

if (every > 0 && Number.isFinite(offset) && offset >= every) {
stale.push(scheduler.key);
}
}

if (page.length < SCHEDULER_PAGE_SIZE) {
break;
}
}

for (const key of stale) {
await queue.removeJobScheduler(key);
}

return stale;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading