Repository navigation
fix(sync): let the stale-issue backfill settle - #218
Merged
Merged
Conversation
- The stale check orders an issue's actions by time instead of text, so backfilled rows, which carry a local UTC offset, sort with the rest. - Backfilled rows are stamped no earlier than the issue's latest action. - The check reads the status that approve nests under "issue". Before, every sync before a project's first pull backfilled the same issues again and pushed duplicate snapshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On a self-hosted td-sync with one Mac and 23 linked projects, every td command appended duplicate issue snapshots to
action_logand pushed them, from 1 to 45 per command depending on the project.BackfillStaleIssuesnever settled. It runs on each sync until a project's first pull, and a device that is a project's only writer never pulls an event, because autosync pulls skip the device's own events. So every sync backfilled the same issues again.The check finds an issue's latest action with
ORDER BY timestamp DESC, which compares text, and two things made it match the same issues after backfilling them:updated_at, bound as atime.Time, so the driver writes2026-09-30 14:20:48.774338-07:00. Logged actions use2026-09-30T21:20:48.774338Z. For the same instant the backfilled row sorts lower, so a statusless action such as adeletestays the latest and the issue looks stale again.approvelogs{"issue": {...}, "created_review_id": ...}. The sync engine unwraps that (unwrapIssuePayload), butstatusMatchesreads only a top-levelstatus, so every approved issue looked stale.Changes:
julianday(timestamp), thenrowid.juliandayparses both formats and SQLite'sCURRENT_TIMESTAMP, which are whataction_logholds after migration 36 rewrote GoString()values. Same-instant ties go to the row logged last.updated_at(or now, without one) and the latest action, so it becomes the latest action even when a statusless action is stamped afterupdated_at.statusMatchesunwraps the nested issue payload.The timestamp format that td writes is unchanged.
Testing
make test,go vet ./..., andgofmtpass. golangci-lint v2.13.1 withGOOS=linuxand Go 1.27.0, as in CI, reports 0 issues.go test -race -count=1 ./internal/sync/ ./test/syncharness/...passes.main: the backfill settles after one run for a deleted issue, when a statusless action is stamped afterupdated_at, and when the issue has noupdated_at; actions in both formats are ordered by time; approve's nested status matches.createbackfilled in the same pass, stamped withcreated_at, does not become the latest action. Ordering byrowidfails this.tdbinaries against a local v0.67.0td-sync. I linked copies of all 23 of my databases, with earlier backfill rows removed so each starts unsynced, then rantd listrepeatedly. With v0.67.0,action_loggrew on every command in all 23. With this branch, it grew in none, and the first sync added fewer backfill rows.Not in this PR
time.Time, so their rows keep a local-offset format that differs from otheraction_logrows. Writing the canonical format would make backfilledcreaterows sort among real actions in readers such asGetLastAction, where they could tie with the action that triggered them intd undo. That seemed worth its own change, along with normalizing existing rows.last_pulled_server_seqat 0. With this change that no longer causes duplicates, so I left the cursor alone. Advancing it past the device's own events would skip them in the manualtd syncpull, which re-applies them for convergence, and would turn off snapshot bootstrap for a relinked database.🤖 Generated with Claude Code