Repository navigation
Conversation
A no-sources MessagesGate reads consumer_cursors only, and the job it blocks is the very job whose poll creates the first cursor row — so with zero rows the gate could never open. A consumer reaches that state on a fresh install, and again whenever cleanup_expired_consumer_cursors deletes every row after a quiet stretch longer than the cursor TTL. With zero cursor rows, report unread whenever any messages exist at all: the job runs once, its poll seeds cursors at MAX(rowid) (backlog still skipped — "new consumers see only future messages" is delivery semantics and unchanged), and the check reverts to cursor-based. Established consumers (>=1 cursor row) still ignore untracked sources. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Nerve PR review · ✅ Ready to merge This closes the gap that c0d9a1e left. That commit made the no- Completeness: DB-level and gate-level tests cover four cases: fresh install, empty DB, after cleanup, and an established consumer. The fresh-install and after-cleanup cases would fail on main. Worth considering:
Checked: full suite in an offline sandbox at |
The bug
A
MessagesGateconfigured without an explicit source list readsconsumer_cursorsonly. The job it gates is the very job whose poll creates the first cursor row — so with zero rows the gate can never open, and the job never runs to create them.A consumer reaches that state in two ordinary ways:
cleanup_expired_consumer_cursorsdeletes every row after a quiet stretch longer than the cursor TTL.The failure is silent: the job is simply skipped forever, and nothing in the logs says why.
The fix
With zero cursor rows, report unread whenever any messages exist at all. The job then runs once, its poll seeds cursors at
MAX(rowid), and the check reverts to the normal cursor-based path.Backlog is still skipped — "new consumers see only future messages" is delivery semantics and is unchanged. Consumers that already have at least one cursor row keep ignoring untracked sources exactly as before, so this only affects the state that was previously unrecoverable.
Tests
New coverage in
tests/test_cron_gates.pyandtests/test_db.pyfor both the zero-row and the established-consumer paths. Full suite green on top of currentmain.