logseq / logseq/db-test

"Validate graph" silently deletes user data: fix defaults to true, logs only at :debug, doesn't bump updated-at, and doesn't refresh the UI

Open
#1,068 0 comments 0 reactions 1 assignee View on GitHub

@tiensonqin is already working on this.

Since Aug 18, 2026.

Dominant language
No language data
Stars
28
Forks
2
PR merge metrics
No merged PRs in 30d

Description

## Summary

`validate-db` runs a series of mutating fix-up passes whenever its `fix`
argument is true, **which is its default**. One of those passes,
`fix-non-closed-values!`, retracts property values. When it does, the user is
told nothing:

1. No notification, and no mention in the validation report.
2. The retraction is printed only at `:debug` to worker stdout.
3. `:block/updated-at` on the affected block is **not** bumped, so no external
tooling or sync can detect the change.
4. **The UI does not refresh.** The block keeps displaying the value it no
longer has until the app is manually reloaded.

Point 4 is the one I'd most like to draw attention to. While reproducing this I
first concluded validation was *not* the cause — because the value was still on
screen after validating. It had already been deleted from the database. A user
who validates, glances at the page, and sees their data intact has been told
something false by the application.

I lost data to this twice, six days apart, and only noticed when a build reading
the property started failing. The second loss removed a value I had manually
restored after the first.

## Version

Logseq DB version, desktop macOS arm64 (build 2287).
`db-worker-node` build-time `2026-07-28T15:15:45Z`, revision `9a11243-dirty`.

## Steps to reproduce

Any state that causes a fix pass to retract will do. The one I hit:

1. Create a property with type **Text** and two closed values.
2. Change its type to **Node** (see companion issue — the closed values are left
behind and become invisible).
3. Set the property on a block to a node value.
4. Run **Validate graph**.
5. Look at the block. **The value is still displayed.**
6. Reload the app. The value is gone.

Querying DataScript directly after step 4 confirms the value is already absent
at that point. Restoring the value and validating again deletes it again, so
this is not a one-time migration artifact.

The only trace:

```
:debug :fix-non-closed-values ([:db/retract 7171 :my.property/fig-source 7014])
```

## Relevant source

From `frontend.worker.db.validate` in the shipped bundle:

```clojure
(defn validate-db
[conn & {:keys [fix] :or {fix true}}] ; fix defaults TRUE
(when fix
(fix-extends-cardinality! conn)
(fix-icon-wrong-type! conn)
(db-migrate/ensure-built-in-data-exists! conn)
(fix-non-closed-values! conn)
(fix-num-prefix-db-idents! conn))

(let [{:keys [errors ...]}
(if fix
(validate-and-fix-invalid-blocks! conn)
...)]
...))
```

Two observations on the shape of this:

**`fix` defaults to `true`.** A command named "Validate graph" mutates the graph
unless a caller opts out. The CLI gets this the other way round — `logseq graph
validate` exposes `-f, --fix` as opt-in — so the two surfaces disagree about
whether validation is a read operation.

**Several call sites take the default without saying so.** `:dev/validate-db`
is one. `export-repo-as-db-edn!` is another, and it validates *before* writing
the file — so a failed or cancelled export still runs the fix passes, and leaves
no artifact to show for it.

**Some passes are considerably broader than their names suggest.**
`fix-icon-wrong-type!` retracts **every** `:logseq.property/icon` datom in the
database if the icon property's `:db/valueType` is `ref`:

```clojure
(defn- fix-icon-wrong-type!
[conn]
(let [icon (d/entity @conn :logseq.property/icon)]
(when (= :db.type/ref (:db/valueType icon))
(let [datoms (d/datoms @conn :avet :logseq.property/icon)
tx-data (cons
[:db/retract (:db/id icon) :db/valueType]
(map (fn [d] [:db/retract (:e d) (:a d)]) datoms))]
(d/transact! conn tx-data {:fix-db? true})))))
```

That may well be correct recovery behaviour. But combined with the silence
above, a user has no way to learn it happened.

## Suggested fixes, in priority order

1. **Notify on any fix pass that retracts user data**, and log above `:debug`.
Include the count in the validation report the user already sees. Silent
deletion is what turned a configuration inconsistency into lost work.
2. **Refresh affected blocks after the fix passes run.** Displaying a value that
has just been deleted is worse than displaying nothing — it actively
convinces the user that nothing happened.
3. **Reconsider `fix` defaulting to `true`** for a user-facing command called
"Validate graph", or rename the command to reflect that it repairs. Matching
the CLI's opt-in `--fix` would make the two surfaces consistent.
4. **Bump `:block/updated-at`** when a fix pass modifies a block, so sync and
external tooling can detect the change.
5. Have `fix-non-closed-values!` skip properties whose current
`logseq.property/type` no longer supports closed values (see companion
issue) — it selects purely on the presence of `:block/_closed-value-property`
without checking the type.

## Impact

A build pipeline reading the affected property failed with 5 unresolved
bindings, roughly twenty hours after an identical run had resolved 34 bindings
cleanly. Nothing changed in between except this retraction, and because
`:block/updated-at` was untouched there was no signal that the graph had changed
at all.

## Related

Companion issue: logseq/db-test#1069 — changing a property's type away from Text leaves its
closed values behind, invisible but still enforced. That is the state that
triggered the deletion here.

## Attachments

Two `db-worker-node-YYYYMMDD.log` files covering both occurrences, available on
request — small, and containing no page content.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.