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

  1. Retry the save — a transient read-after-write miss usually succeeds on a second attempt.
  2. Check for concurrent deletes/automations racing on the same row and serialize them.
  3. Verify the workspace DB is healthy (replication, CouchDB logs) if this happens repeatedly.
  4. 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

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


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/f8b9518bad846aea. Report an issue: GitHub.