`qmd embed` still fails mid-run with DisposedError on 2.8.3, and still exits 0
Refiling as invited: #124 and #71 were closed on 2026-05-20 with "If it still reproduces on v2.5.1 or newer, please open a fresh focused issue with current repro steps." It does, on 2.8.3 (facd35e). Current steps below.
Environment: qmd 2.8.3 (facd35e), Windows 11, ThinkPad X1 (2023, Iris Xe), CPU only, embeddinggemma-300M-Q8_0. ~800 markdown files, 14 collections, ~7.5 MB.
On a 5.4 MB collection (485 files), qmd embed runs about 35 minutes, reaches ~46%, then
every remaining chunk fails:
Embedding error for text: DisposedError: Object is disposed
at LlamaContextSequence._ensureNotDisposed (node-llama-cpp/.../LlamaContext.js:1902)
at LlamaContextSequence._eraseContextTokenRanges (.../LlamaContext.js:998)
at LlamaContextSequence.eraseContextTokenRanges (.../LlamaContext.js:994)
at .../LlamaEmbeddingContext.js:40
at withLock (lifecycle-utils/dist/withLock.js:23)
at LlamaEmbeddingContext.getEmbeddingFor (.../LlamaEmbeddingContext.js:39)
at qmd/dist/llm.js:1124
at async Promise.all (index 0)
at async LlamaCpp.embedBatch (qmd/dist/llm.js:1115)
at async LLMSession.withOperation (qmd/dist/llm.js:1568)Smaller collections (24 KB to 887 KB) complete cleanly every time. Embedding runs at 3-5 KB/s on this hardware, so the collections that fail are simply the ones running longest. It looks duration-related rather than content-related.
Which timeout, and what we can and cannot tell you. These runs did not pass
--timeout, so qmd's default 30-minute session cap was in force — and 35 minutes of wall
clock against a 30-minute cap makes #124's maxDuration the obvious candidate. We have
not been able to test --timeout 0 on the machine that reproduces it, so we cannot rule
out the other disposer, and we would rather say so than guess:
- #124 (
SessionReleasedError, exactly 30m 4s, count frozen, run continues to 100%) is the session cap. - #40 and #450 report this error class —
DisposedError: Object is disposedraised from_ensureNotDisposed, which is what we see, notSessionReleasedError— and attribute it to the inactivity timeout. #40 proposes callingtouchActivity()after each successful embedding inembedBatch()rather than once per batch.
So we match #124 on timing and #40/#450 on error class. Those have different fixes, and from outside the process we cannot separate them. If it is useful, tell us what to run and we will run it — the hardware that reproduces this reliably is a machine we own.
Worth noting from #450's thread: it is not CPU-only. A commenter reproduced it on an M1 Pro with Metal offloading active.
The exit code is the part that costs downstream users most, and it is independent of whichever timeout is at fault. That run printed six stack traces, skipped hundreds of chunks, and returned 0. Our tooling reported a finished index over a half-embedded collection and every health check agreed with it. We spent hours treating a slow progress bar as a hang, because nothing in the exit status distinguished the two. #71 was the same failure seen from this side, and #124 drew the same complaint in its thread.
Even without the lifecycle fix, either of these would have saved that time:
- non-zero exit when chunks were skipped
- a summary line —
embedded 1338, skipped 363— that a wrapper can read without parsing stack traces
This one is cheap, and it converts a silent half-index into a visible failure.
Our workaround, for anyone who lands here: embed one collection at a time, and treat
Embedding error, Session expired or skipping in the output as incomplete regardless
of exit code, retrying that collection up to three times. Progress persists per chunk so
retries resume — #450's commenter reports the same. Thirteen of fourteen collections then
complete first time. It works, and it is built on parsing log wording, so it will break.
Source: tobi/qmd