Reading a run — what was ingested, skipped, quarantined
A run cannot look clean while it lost rows. Every row read is written, skipped, or set aside with its reason:
IN = OUT + SKIPPED.
The counters
$ lm run get 8ffcd5ae
ID CONNECTOR ENDPOINT STATUS IN OUT SKIPPED DEAD-LETTERS STARTED ENDED
8ffcd5ae orders default Completed 10 8 2 2 2026-10-19 01:47 2026-10-19 01:47
⚠ 2 rows quarantined — see: lm dlq list --run 8ffcd5ae-c387-45b9-8a57-091043af6c59
10 = 8 + 2, and both skipped rows are dead letters.
| Counter | Counts |
|---|---|
IN |
the rows read from the source |
OUT |
the source rows delivered to the table. A row already there, unchanged, counts too. A row split-row cut in three still counts one |
SKIPPED |
the rows not written: filtered out on purpose, or set aside |
DEAD-LETTERS |
the part of SKIPPED set aside with its reason, for you to look at |
| Not written | Reasons | A dead letter? |
|---|---|---|
| on purpose | a filter-where or skip-empty-rows dropped it; parser.short_rows: skip-leading skipped a line before the table |
no |
| set aside | MalformedSource (the file is broken), MappingFailed (a hook could not read a value), ValidationFailed (validate-row), WriteFailed |
yes |
The lines under the table
lm run get adds a line for each of these, on stderr:
| Line | Means | Do this |
|---|---|---|
⚠ N rows quarantined — see: lm dlq list --run … |
rows set aside | Diagnosing a failed run |
⚠ N cell(s) not delivered as declared (a hook, or a promoted value of the wrong type) — the rows were still written |
a value a hook could not read was emptied, or left as it was; or a promoted value did not read as its type and was emptied | cell_errors |
• N row(s) removed — the source no longer holds them |
a one-file csv-file connector followed its file |
Full refresh |
• N file(s) unchanged since they were last read — not read again |
the file did not change: IN 0 is normal |
to read it again: lm endpoint reset-cursor <connector>/<endpoint> |
• N file(s) read but not archived — still in the source folder … |
the archive step failed; the rows are in | check the archive folder's rights |
• script hooks:… sha256 … |
the Kotlin script version the run used | Kotlin hooks |
✖ run failed: …, • run partial: …, • run cancelled: … |
why the run ended so | read the reason; it names the gesture |
A full refresh that would remove too much fails, and removes nothing:
$ lm run get a569be52
ID CONNECTOR ENDPOINT STATUS IN OUT SKIPPED DEAD-LETTERS STARTED ENDED
a569be52 orders default Failed 5 5 0 0 2026-10-19 01:47 2026-10-19 01:47
✖ run failed: 3 of 8 rows would be removed: the file holds 5 where the table held 8, more than refresh.max_removed (30%) allows — nothing was removed. If the file really lost them, raise refresh.max_removed (or set it to off) and run again.
From a count to the row
lm dlq list --run <id> lists the dead letters; lm dlq get <id> shows one, with the row exactly
as it was read. Fix the source, run again, then close the entry. The walk, step by step:
Diagnosing a failed run.
In your monitoring
/q/metrics counts runs by status and dead letters by reason:
Observability.