Skip to content

fix(sync): filter logId=Int64.max placeholder to prevent cursor wedge - #17

Open
wootom wants to merge 1 commit into
silver-flight-group:mainfrom
wootom:fix/sync-int64-max-cursor-wedge
Open

fix(sync): filter logId=Int64.max placeholder to prevent cursor wedge#17
wootom wants to merge 1 commit into
silver-flight-group:mainfrom
wootom:fix/sync-int64-max-cursor-wedge

Conversation

@wootom

@wootom wootom commented May 15, 2026

Copy link
Copy Markdown

KakaoTalk Mac persists in-flight (server-ack pending) outgoing messages in NTChatMessage with logId = Int64.max as a temporary placeholder. If DatabaseWatcher updates its lastLogId cursor from such a row, every subsequent messagesSince(logId > ?) query becomes WHERE logId > Int64.max, which is always empty — sync enters a silent wedge state until the process restarts, even though the polling loop keeps running and no error is raised.

Repro

Send two messages via kakaocli send in quick succession (~1s apart) while kakaocli sync --follow runs. The second send's echo is picked up before KakaoTalk receives server ack, sync emits a SyncMessage with log_id: 9223372036854775807, and from that point onwards no new messages reach the webhook. Field-observed on a bot integrating kakaocli sync via webhook (vaaxbot, ~10h runtime).

Fix

  • DatabaseWatcher: filter rows with logId == Int64.max out of both the emission batch and the cursor-max calculation. The same message is re-read on the next poll once KakaoTalk updates the row with the real server-assigned logId, so no events are lost.
  • DatabaseReader.maxLogId(): exclude Int64.max rows from the MAX(logId) query so that startup cursor seed cannot be poisoned if a placeholder is already in the table when sync starts.

Tests

Existing swift test 4/4 still passes. No new unit test added: the failure requires a live KakaoTalk Mac UI in a specific timing window; a deterministic test would need a recorded SQLite fixture with a placeholder row, which is left as follow-up.

KakaoTalk Mac persists in-flight (server-ack pending) outgoing messages
in NTChatMessage with logId = Int64.max as a temporary placeholder.
If DatabaseWatcher updates its lastLogId cursor from such a row, every
subsequent messagesSince(logId > ?) query becomes WHERE logId > Int64.max,
which is always empty — sync enters a silent wedge state until the
process restarts, even though the polling loop keeps running and no
error is raised.

Repro
-----
Send two messages via kakaocli send in quick succession (~1s apart)
while kakaocli sync --follow runs. The second send's echo is picked up
before KakaoTalk receives server ack, sync emits a SyncMessage with
log_id: 9223372036854775807, and from that point onwards no new
messages reach the webhook. Field-observed on a bot integrating
kakaocli sync via webhook (vaaxbot, ~10h runtime).

Fix
---
- DatabaseWatcher: filter rows with logId == Int64.max out of both the
  emission batch and the cursor-max calculation. The same message is
  re-read on the next poll once KakaoTalk updates the row with the
  real server-assigned logId, so no events are lost.
- DatabaseReader.maxLogId(): exclude Int64.max rows from the MAX(logId)
  query so that startup cursor seed cannot be poisoned if a placeholder
  is already in the table when sync starts.

Tests
-----
Existing swift test 4/4 still passes. No new unit test added: the
failure requires a live KakaoTalk Mac UI in a specific timing window;
a deterministic test would need a recorded SQLite fixture with a
placeholder row, which is left as follow-up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant