deleted pages older than 30 days cause checksum differences between client and server

オープン
#1,513 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

@tiensonqin がすでに取り組んでいます。

2026年11月9日 から。

  • #13990 @tiensonqin による — オープン

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
48/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
clojure

調査の方向性

Start with src/main/frontend/worker/handler/transaction.cljs, deps/outliner/src/logseq/outliner/recycle.cljs, and src/main/frontend/worker/sync/apply_txs.cljs, following the issue’s links to the GC and transaction persistence paths. Reproduce with the CLI steps in the issue and compare local and remote checksums before and after 31 days. Done means recycle GC no longer leaves the client and server checksums inconsistent.

索引モデルが issue の本文から書いたものです。

説明

Search first
  • I searched and no similar issues were found
What Happened?

Once a page has been deleted for more than 30 days, it will be deleted by local cleanup, but the deletion is never sent to the server. This means that the local checksum changes, but the server checksum doesn't, and from there on they remain inconsistent.

This is quite possibly the underlying cause of #1341 , but I couldn't tell for sure so I've opted to create as a new more specific issue and link from there. Once this is fixed, the fix can be tried against #1341 to see if it was indeed the same problem.

Reproduce the Bug

Here are reproduction instructions using the CLI. These instructions use faketime to pretend a month has passed for convenience, but I have a real case of this issue, which I've seen with both the CLI and the desktop client, and both have the same checksum that's different to the server checksum.

I've put Claude's theory as to why this is happening into "Additional Context" below, but I wanted to start with something that made sense to me as a human!

An alias for convenience. This runs the Logseq CLI, using the Linux faketime command to make it think the time is different from the server's current time.

LOGSEQ_AT() { _offset="$1"; shift; FAKETIME_DONT_FAKE_MONOTONIC=1 faketime -f "$_offset" node /opt/logseq/static/logseq-cli.js --root-dir /data --graph recycle-gc-repro-2 --output json "$@"; }
  1. Create a new graph and sync it
$ LOGSEQ_AT +0d graph create --enable-sync
{"status":"ok","data":{"graph":"recycle-gc-repro-2","repo":"logseq_db_recycle-gc-repro-2", ...}}
$ LOGSEQ_AT +0d sync start
{"status":"ok","data":{"last-error":null,"ws-state":"open","local-tx":0,"pending-local":0,"pending-asset":0,"pending-server":0,"remote-tx":0,"local-checksum":"46fd0cee68a8017e","remote-checksum":"46fd0cee68a8017e","missing-asset-upload-files":[],"graph-id":"ebc6f1b5-26d7-4ad8-acec-ea720f22fee2","repo":"logseq_db_recycle-gc-repro-2"}}
  1. Add a page and remove it, so it ends up in the recycle
$ LOGSEQ_AT +0d upsert page --page Repro2
{"status":"ok","data":{"result":[196]}}
$ LOGSEQ_AT +0d upsert block --target-page Repro2 --content "delete me too"
{"status":"ok","data":{"result":[197]}}
$ LOGSEQ_AT +0d remove page --page Repro2
{"status":"ok","data":{"result":true,"permanently-deleted?":false}}

Confirming it's in the recycling ("result: 1"):

$ LOGSEQ_AT +0d query --query '[:find (count ?e) . :where [?e :logseq.property/deleted-at]]'
{"status":"ok","data":{"result":1}}

and confirm that local and remote checksums are the same, in this case 22deee402252901e:

$ LOGSEQ_AT +0d sync status
{"status":"ok","data":{"last-error":null,"ws-state":"open","local-tx":3,"pending-local":0,"pending-asset":0,"pending-server":0,"remote-tx":3,"local-checksum":"22deee402252901e","remote-checksum":"22deee402252901e","missing-asset-upload-files":[],"graph-id":"ebc6f1b5-26d7-4ad8-acec-ea720f22fee2","repo":"logseq_db_recycle-gc-repro-2"}}

Stop the worker process, pretend 31 days have passed, and start the sync again:

$ LOGSEQ_AT +0d server stop
[graph-lifecycle] {"event":"worker-termination","repo":"recycle-gc-repro-2","graphsDir":"/data/graphs","generation":"8921c430-d5ae-41da-957e-a7431b0dc5f6","ticket":"eb9db5f6-e6e4-4e8d-ab0b-e337fff0b9f6","pid":982,"stage":"graceful"}
{"status":"ok","data":{"repo":"logseq_db_recycle-gc-repro-2"}}
$ LOGSEQ_AT +31d sync start
{"status":"ok","data":{"last-error":null,"ws-state":"open","local-tx":3,"pending-local":1,"pending-asset":0,"pending-server":0,"remote-tx":3,"local-checksum":"46fd0cee68a8017e","remote-checksum":"22deee402252901e","missing-asset-upload-files":[],"graph-id":"ebc6f1b5-26d7-4ad8-acec-ea720f22fee2","repo":"logseq_db_recycle-gc-repro-2"}}

Once the sync has settled, notice that the local checksum has changed, and no longer matches the remote checksum (which hasn't changed):

$ LOGSEQ_AT +31d sync status
{"status":"ok","data":{"last-error":null,"ws-state":"open","local-tx":4,"pending-local":0,"pending-asset":0,"pending-server":0,"remote-tx":4,"local-checksum":"46fd0cee68a8017e","remote-checksum":"22deee402252901e","missing-asset-upload-files":[],"graph-id":"ebc6f1b5-26d7-4ad8-acec-ea720f22fee2","repo":"logseq_db_recycle-gc-repro-2"}}

See also that the recycled page has now been deleted on the client, presumably by the recycle GC process when the new process started ("result: null"):

$ LOGSEQ_AT +31d query --query '[:find (count ?e) . :where [?e :logseq.property/deleted-at]]'
{"status":"ok","data":{"result":null}}
Expected Behavior

After the recycle GC, local and server checksums still match.

Screenshots

No response

Files

No response

Browser, Desktop or Mobile Platform Information
  • Windows 11
    • desktop app version 2.0.2
  • Linux ARM64 running in Docker node:24.19.0-bookworm-slim container, OS version Debian bookworm
    • CLI app version 2.0.2 (Build time: 2026-10-07T14:07:14.501Z, Revision: 533ac43)
Additional Context

This is something I hit in my real database, and used Claude to help track it back to the reproducible case above. Here's Claude's description of what it thinks is happening:

Once a recycled page or block passes the 30-day retention, the next recycle GC on each client permanently deletes it locally only. The deletion is never sent to the sync server, so the server keeps the entity, and the client's local checksum (which counts it) no longer matches the server's. Nothing is pending and content still syncs, but the checksums never agree again.

Cause, as far as I can read it (permalinks to master d3b4ea8; 2.0.2 is the same):

  • maybe-run-recycle-gc! runs at graph open (db_core.cljs#L862) and after transactions, at most once per 24 h.
  • outliner-recycle/gc! retracts entities recycled more than 30 days ago with {:outliner-op :recycle-gc :persist-op? false}.
  • persistable-local-tx-meta? rejects :persist-op? false, so enqueue-local-tx! never queues the GC for upload.
  • The sync checksum counts recycled entities like any other block or page, and the server has no recycle GC of its own, so the server keeps them indefinitely.

Side effects: the sync indicator for every affected client is in a permanent mismatch, the sync indicator still shows green (an indicator fix, logseq/logseq#13169, was closed unmerged); a fresh download seems to bring long-expired items back into Recycle until that copy's next GC; the server's copy grows with entities every client has deleted.

Also seen while investigating: GET /sync//checksum/diagnostics returns HTTP 500 for my actual (E2EE) graph, so "(Dev) Recompute graph checksum" can't show a diff.

Some options for a fix:

  • sync the GC deletion (drop :persist-op? false from gc!; retracting an already-retracted entity should be a no-op on other clients), or
  • run the same GC on the server, or
  • leave recycled entities out of the checksum on both sides.
Are you willing to submit a PR? If you know how to fix the bug.
  • I'm willing to submit a PR (Thank you!)
主要言語
言語のデータがありません
スター
29
フォーク
2
PR マージ指標
30日以内にマージされた PR はありません

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

logseq/db-test のほかの issue

logseq/db-test の issue をすべて見る

似ている issue

Databases の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。