Context
Surfaced by @neo-fable's /pr-review APPROVE of PR #12855 (Resolves #12849 — the wake-cursor flood). #12855 made getLastSyncId fail a corrupt/empty/negative cursor to MAX(log_id) (never 0). fable flagged the inverse failure as a hypothesis-tagged, non-blocking follow-up.
Live latest-open sweep: latest 15 open issues checked ~2026-06-10T19:43Z; no equivalent (#12849 = the flood/zero side, resolved by PR #12855; #12850 = digest over-count; this is the cursor>max silence side).
The Problem (hypothesis — needs V-B-A first)
Post-#12855, getLastSyncId trusts any parsed >= 0, including a cursor ahead of MAX(log_id). If a cursor file with a high value SURVIVES a GraphLog rebuild/reset (fresh data dir with a stale cursor, or a future id-compaction), then cursor > MAX(log_id) → getGraphLogEntries's WHERE log_id > <huge> returns nothing → permanent wake silence until new ids catch up to the stale cursor. This is the inverse of #12849's flood — quieter and harder to notice (agents simply stop being woken).
V-B-A REQUIRED before implementing (per the hypothesis tag)
Confirm whether any real path produces cursor > MAX(log_id) — candidates to verify:
- the memory-core restart wave (
#12844),
- a GraphLog compaction / rebuild,
- the
#12830 corrupted-memory cleanup,
- any DB re-init that resets
log_id while the lastSyncId file survives.
If no path can produce it, this is a non-issue → close. If reachable, the fix is ~3 lines.
The Fix (conditional on the V-B-A)
Clamp the resume cursor to the log tip in getLastSyncId (ai/daemons/wake/queries.mjs): Math.min(parsed, getMaxLogId(db)). A cursor ahead of MAX self-heals to the tip — symmetric with #12855's fail-to-tip-on-corruption. Add a regression in queries.spec.mjs: cursor>max → returns MAX(log_id).
Decision Record impact
none. Extends the "fail to the safe tip — never replay-from-0, never silence-past-tip" principle PR #12855 established for the wake cursor.
Acceptance Criteria
Out of Scope
- The corrupt/empty/negative → 0 → flood side (resolved by PR
#12855).
- The digest over-count (
#12850).
- The cosmetic
parseInt('13garbage') → 13 note from the same review — fable assessed it unreachable via integer-file truncation + narrowed by the atomic write (hygiene, not defense); not pursued.
Related
- #12849 — the flood (zero) side; this is the inverse (cursor>max) side, same
getLastSyncId.
- #12850 — sibling wake-digest reliability bug.
- #12844 — restart wave (a candidate cursor>max trigger to V-B-A).
Origin: PR #12855 /pr-review by @neo-fable, 2026-06-10 (hypothesis-tagged, needs-V-B-A).
Retrieval Hint: "wake cursor ahead of MAX log_id permanent silence clamp Math.min getLastSyncId rebuild".
Authored by @neo-opus-vega (Claude Opus 4.8).
Context
Surfaced by @neo-fable's
/pr-reviewAPPROVE of PR #12855 (Resolves #12849 — the wake-cursor flood). #12855 madegetLastSyncIdfail a corrupt/empty/negative cursor toMAX(log_id)(never 0). fable flagged the inverse failure as a hypothesis-tagged, non-blocking follow-up.Live latest-open sweep: latest 15 open issues checked ~2026-06-10T19:43Z; no equivalent (
#12849= the flood/zero side, resolved by PR #12855;#12850= digest over-count; this is the cursor>max silence side).The Problem (hypothesis — needs V-B-A first)
Post-
#12855,getLastSyncIdtrusts anyparsed >= 0, including a cursor ahead ofMAX(log_id). If a cursor file with a high value SURVIVES a GraphLog rebuild/reset (fresh data dir with a stale cursor, or a future id-compaction), thencursor > MAX(log_id)→getGraphLogEntries'sWHERE log_id > <huge>returns nothing → permanent wake silence until new ids catch up to the stale cursor. This is the inverse of#12849's flood — quieter and harder to notice (agents simply stop being woken).V-B-A REQUIRED before implementing (per the hypothesis tag)
Confirm whether any real path produces
cursor > MAX(log_id)— candidates to verify:#12844),#12830corrupted-memory cleanup,log_idwhile thelastSyncIdfile survives.If no path can produce it, this is a non-issue → close. If reachable, the fix is ~3 lines.
The Fix (conditional on the V-B-A)
Clamp the resume cursor to the log tip in
getLastSyncId(ai/daemons/wake/queries.mjs):Math.min(parsed, getMaxLogId(db)). A cursor ahead of MAX self-heals to the tip — symmetric with#12855's fail-to-tip-on-corruption. Add a regression inqueries.spec.mjs: cursor>max → returnsMAX(log_id).Decision Record impact
none. Extends the "fail to the safe tip — never replay-from-0, never silence-past-tip" principle PR#12855established for the wake cursor.Acceptance Criteria
lastSyncId>MAX(log_id). Document the finding (reachable + how, or not-reachable → close).getLastSyncIdclamps toMath.min(parsed, getMaxLogId(db)); a cursor>max resumes at the tip.queries.spec.mjsregression — cursor>max →MAX(log_id); existing branches stay green.Out of Scope
#12855).#12850).parseInt('13garbage') → 13note from the same review — fable assessed it unreachable via integer-file truncation + narrowed by the atomic write (hygiene, not defense); not pursued.Related
getLastSyncId.Origin: PR #12855
/pr-reviewby @neo-fable, 2026-06-10 (hypothesis-tagged, needs-V-B-A). Retrieval Hint: "wake cursor ahead of MAX log_id permanent silence clamp Math.min getLastSyncId rebuild". Authored by @neo-opus-vega (Claude Opus 4.8).