Budibase/budibase · error
Unable to retrieve row ${row._id} after saving.
Error message
Unable to retrieve row ${row._id} after saving. What it means
After writing a row with db.put(), finaliseRow immediately re-reads the row with db.tryGet() so it can merge the stored document (with its new _rev) back into the enriched row for formula processing. If the read returns nothing the save pipeline cannot continue, so it throws. This normally indicates the row vanished between the put and the get, or the _id is inconsistent.
Source
Thrown at packages/server/src/api/controllers/row/staticFormula.ts:168
let enrichedRow = await outputProcessing(source, cloneDeep(row), {
squash: false,
})
// use enriched row to generate formulas for saving, specifically only use as context
row = await processFormulas(table, row, {
dynamic: false,
contextRows: [enrichedRow],
})
if (updateAIColumns) {
row = await processAIColumns(table, row, {
contextRows: [enrichedRow],
})
}
await db.put(row)
const retrieved = await db.tryGet<Row>(row._id)
if (!retrieved) {
throw new Error(`Unable to retrieve row ${row._id} after saving.`)
}
delete enrichedRow._rev
enrichedRow = mergeRows(retrieved, enrichedRow)
enrichedRow = await processFormulas(table, enrichedRow, {
dynamic: false,
})
// this updates the related formulas in other rows based on the relations to this row
if (updateFormula) {
await updateRelatedFormula(table, enrichedRow)
}
const squashed = await linkRows.squashLinks(source, enrichedRow)
return { row: enrichedRow, squashed, table }
}
View on GitHub (pinned to a81a902e9a)
Solutions
- Retry the save — a transient read-after-write miss usually succeeds on a second attempt.
- Check for concurrent deletes/automations racing on the same row and serialize them.
- Verify the workspace DB is healthy (replication, CouchDB logs) if this happens repeatedly.
- Inspect the row document's _id for corruption or undefined values before saving.
Defensive patterns
Strategy: retry
Validate before calling
// before saving, ensure row has a valid _id
if (!row._id) {
throw new Error("Row must have an _id before saving")
} Type guard
function isStoredRow(row: Row | undefined): row is Row {
return !!row && typeof row._id === "string"
} Try / catch
try {
await saveRow(row)
} catch (err) {
if (err.message.startsWith("Unable to retrieve row")) {
// transient read-after-write miss — retry once with backoff
await new Promise(r => setTimeout(r, 100))
return saveRow(row)
}
throw err
} Prevention
- Avoid concurrent automations writing and deleting the same row.
- Monitor CouchDB health/replication lag.
- Always persist rows with a defined _id and _rev.
When it happens
Trigger: A concurrent delete removed the row between db.put(row) and db.tryGet(row._id) in finaliseRow at packages/server/src/api/controllers/row/staticFormula.ts:168; a database replication/consistency lag where the freshly written doc is not yet readable; a corrupted or missing _id on the row document.
Common situations: Race conditions where two automations operate on the same row (one saving, one deleting); CouchDB sync issues in clustered deployments; save hooks (updateRelatedFormula) cascading onto rows that another process just removed.
Related errors
- DB does not exist
- CouchDB error: ${err.message}
- Legacy view metadata is missing
- Column names can't contain special characters
- Cannot re-use the linked column name for a linked table.
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/f8b9518bad846aea.
Report an issue: GitHub.