HTTP API: write calls hang until client timeout while the write succeeds server-side (read path unaffected)
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 28
- Forks
- 2
- PR merge metrics
- No merged PRs in 30d
Description
### Search first
- [x] I searched and no similar issues were found
### What Happened?
On the DB version, the local HTTP API (`127.0.0.1:12315`) intermittently enters a state — lasting from minutes to **9+ hours** — where **write operations hang until the client times out**, while read operations stay fast (0.02–0.03 s) throughout.
The writes are **not rejected: they are executed server-side**. The blocks appear in the journal, but the API never returns the block object, so the caller has no UUID and cannot verify, adopt, or clean up what it just created.
Observed over 5 consecutive days on the same installation, with three distinct signatures.
**Signature 1 — `insertBlock` hangs, `appendBlockInPage` fine (2026-08-03 23:00 → 2026-08-04 07:00)**
Six consecutive scheduled runs, all identical:
```
23:00:05 appendBlockInPage -> OK in 0.04s
23:00:05 insertBlock -> no response, client timeout at exactly 25.0s
23:00:31 insertBlock retry -> no response, client timeout at exactly 25.0s
```
Same at 23:30, 05:30, 06:00, 06:30, 07:00. Every time, `appendBlockInPage` returned in **0.04–0.08 s** while `insertBlock`, issued seconds later against the same graph, never returned. The 25.0 s value is our client timeout, not a server error.
**Signature 2 — every write hangs, including `appendBlockInPage` (2026-08-04, ~08:40)**
A few hours later the same `appendBlockInPage` call that had been returning in 0.04 s stopped returning at all:
```
datascriptQuery -> OK in 0.02s
appendBlockInPage -> no response, client timeout at 30.0s
appendBlockInPage -> no response, client timeout at 40.0s (longer timeout)
```
Tested with both the page **name** and the page **UUID** as first argument — same result. **Restarting the app did not clear it.**
**Decisive observation:** all three blocks from those timed-out calls were present in the journal afterwards (screenshot attached). The writes had been committed; only the response never came back.
**Signature 3 — `getPageBlocksTree` permanently stalls (2026-07-31)**
Earlier, a different call failed the same way: `getPageBlocksTree` hung on 4 out of 4 pages even with a 300 s timeout, and survived an app restart, while `getCurrentGraph` (0.03 s), `getPage` (0.01 s) and `datascriptQuery` (0.01 s) were normal. Worked around by rebuilding the tree from `datascriptQuery` via `:block/parent` and `:block/order`.
**What we ruled out**
Two diagnostic scripts exercise the failing patterns. When the API is healthy all of these pass in under 0.1 s, which excludes the structural explanations:
- inserting into a parent created moments earlier (parent queryable via Datascript after 0.01 s)
- three consecutive `insertBlock` calls
- `insertBlock` under a parent itself created with `insertBlock` (the exact chain that fails in production)
- nesting depth (3 levels)
- bursts of 5 inserts under one parent
- cold-start latency alone — the first write of a session costs 7.2 s and one later measured 14.5 s, but failures also occur right after a write completed in 0.04 s
So it is not a call pattern, nesting shape, or sequence length. It is a **state** the API enters and leaves with no observable trigger.
**Impact**
We run a nightly job writing a ~60-block tree into the journal. Since writes execute without acknowledging, the client cannot tell "failed" from "succeeded silently":
- retries create **duplicate blocks** unless every write is followed by a Datascript lookup by content
- rollback cannot remove what it cannot identify — **14 orphaned blocks** accumulated in one journal over 9 hours
- a write cycle that normally takes ~5 s takes **90+ s** before failing, since each call burns its full timeout
Possibly related: #1032 (`addPropertyValueChoices` silently failing worker-side) — same theme of plugin API calls not reporting their outcome.
### Reproduce the Bug
The faulted state appears without warning and cannot be triggered on demand. What reliably exposes it is a repeated scheduled workload rather than a one-off call.
1. Enable the HTTP API server (`127.0.0.1:12315`) with a token, on a DB graph.
2. From an external script, on a schedule (we use every 30–60 min), write a nested block tree into today's journal: one `insertBlock` for a container under an existing block, then one `insertBlock` per child under that container (~60 calls total).
3. Set a client timeout and log the elapsed time of every call, plus a `datascriptQuery` before each run as a control.
4. Let it run across a full day/night cycle.
5. When the fault appears, observe: `datascriptQuery` still answers in ~0.02 s, while the write call never returns and burns the entire client timeout.
6. Open the journal in the UI: the blocks from the timed-out calls **are there**.
Minimal probe showing the split between read and write (Python, stdlib only):
```python
import json, time, urllib.request
TOKEN = ""
def call(method, args, to=40):
b = json.dumps({"method": method, "args": args}).encode()
r = urllib.request.Request("http://127.0.0.1:12315/api", data=b,
headers={"Content-Type": "application/json",
"Authorization": "Bearer " + TOKEN}, method="POST")
t = time.time()
try:
raw = urllib.request.urlopen(r, timeout=to).read().decode()
return (json.loads(raw) if raw.strip() else None), time.time() - t
except Exception as e:
return ("ERR:" + type(e).__name__), time.time() - t
q = '[:find (pull ?p [:db/id]) :where [?p :block/journal-day 20260804]]'
print("read :", call("logseq.DB.datascriptQuery", [q])[1])
print("write :", call("logseq.Editor.appendBlockInPage", ["Aug 4th, 2026", "PROBE"]))
```
In the faulted state this prints a read around 0.02 s and a write ending in `ERR:TimeoutError` — and `PROBE` is nevertheless in the journal.
### Expected Behavior
Two things would make this recoverable from the client side even if the root cause is hard to fix:
1. **Fail fast instead of hanging.** An error response — even a generic one — is far more actionable than an unbounded hang, because the caller can then decide whether to retry. Right now the only signal is the client's own timeout, which arrives long after the write has already been committed.
2. **Never commit a write without returning its identity.** A timed-out write is currently indistinguishable from a rejected one, which forces callers into content-matching heuristics and risks duplicates on retry. Returning the block UUID before the write settles, or offering a way to correlate a request with the block it created, would remove that whole class of failure.
Additionally: reads and writes appear to have independent health. A client cannot probe the write path without actually writing, which is why our pre-flight check (a throwaway `appendBlockInPage` + `removeBlock`) reported the API as healthy in 0.04 s seconds before a write hung for 25 s.
### Screenshots
### Files
Excerpt from our job log (`insertBlock` failing at exactly the client timeout, right after a successful `appendBlockInPage`):
```
[2026-08-03 23:00:05] Canary write OK (0.04s) <- appendBlockInPage
[2026-08-03 23:00:31] insertBlock failed (attempt 1/3), waiting 2s
[2026-08-03 23:00:58] insertBlock failed (attempt 2/3), waiting 5s
[2026-08-03 23:01:28] write failed/incomplete - partial removed, previous content preserved
[2026-08-04 05:30:05] Canary write OK (0.05s)
[2026-08-04 05:30:30] insertBlock failed (attempt 1/3), waiting 2s
[2026-08-04 05:30:57] insertBlock failed (attempt 2/3), waiting 5s
[2026-08-04 05:31:28] write failed/incomplete - partial removed, previous content preserved
```
Latency between commit and visibility when the API is healthy, for reference:
```
appendBlockInPage -> confirmed in 3.37s
block visible via datascriptQuery -> 3.55s after the call (~0.2s after the response)
removeBlock -> 0.07s, verified removed
```
I have two standalone Python diagnostic scripts (stdlib only) that measure this — one runs a matrix of insert/append patterns with timings, the other measures the delay between a write and its visibility in Datascript. Happy to attach them or post a gist if useful.
### Browser, Desktop or Mobile Platform Information
macOS 26.6 (Tahoe), Apple Silicon M4, 24 GB — Logseq Desktop App v2.0.1 (DB version, beta)
DB graph imported from a file-based graph, ~600 entities. Calls made directly over HTTP from Python (stdlib urllib), no plugins involved.
### Additional Context
The fault has never been reproducible on demand: every time we pointed a diagnostic at it, it had already cleared. It was only observable against the live scheduled workload. In our case it appeared most often — but not exclusively — between 21:00 and 07:00 local time, though we have no evidence that the hour itself matters.
One workaround that may be useful to other API consumers, and which also hints at where the problem might be: `insertBatchBlock` performs 2 HTTP round-trips regardless of child count, versus `1 + N` for `insertBlock`. Building a tree in a single batch call drastically reduces exposure to this fault. We have not yet been able to test whether `insertBatchBlock` also hangs in the faulted state, precisely because the fault clears whenever we try to measure it.
### Are you willing to submit a PR? If you know how to fix the bug.
- [ ] I'm willing to submit a PR (Thank you!)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the local HTTP `/api` endpoint and use the supplied Python minimal probe to compare `datascriptQuery` with `appendBlockInPage` in healthy and faulted states. Review the mentioned write methods and diagnostic scripts, then establish a reproducible failure or bounded test; done should mean timed-out writes return an error or an identity that lets callers distinguish committed writes from rejected ones.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100