{"record":{"id":"bdc977c9e0fc069f","repo":"remix-run/remix","slug":"invalid-beforedelete-callback-result-for-table","errorCode":null,"errorMessage":"Invalid beforeDelete callback result for table \"' + context.tableName + '\"","messagePattern":"Invalid beforeDelete callback result for table \"' \\+ context\\.tableName \\+ '\"","errorType":"validation","errorClass":"DataTableValidationError","httpStatus":null,"severity":"error","filePath":"packages/data-table/src/lib/database/write-lifecycle.ts","lineNumber":205,"sourceCode":"): void {\n  let callback = getTableBeforeDelete(table)\n\n  if (!callback) {\n    return\n  }\n\n  let callbackResult = callback(context)\n  assertSynchronousCallbackResult(context.tableName, 'delete', 'beforeDelete', callbackResult)\n\n  if (callbackResult === undefined) {\n    return\n  }\n\n  if (hasIssues(callbackResult)) {\n    throwValidationIssues(context.tableName, callbackResult.issues, 'delete', 'beforeDelete')\n  }\n\n  throw new DataTableValidationError(\n    'Invalid beforeDelete callback result for table \"' + context.tableName + '\"',\n    [{ message: 'Expected beforeDelete to return nothing or { issues }' }],\n    {\n      metadata: {\n        table: context.tableName,\n        operation: 'delete',\n        source: 'beforeDelete',\n      },\n    },\n  )\n}\n\nexport function runAfterWriteHook<table extends AnyTable>(\n  table: table,\n  context: TableAfterWriteContext<TableRow<table>>,\n): void {\n  let callback = getTableAfterWrite(table)\n","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table/src/lib/database/write-lifecycle.ts#L187-L223","documentation":"A beforeDelete hook may return nothing (undefined) to allow the delete, or { issues } to abort it with validation problems. Any other non-issue return value is rejected with a DataTableValidationError, because there is no meaningful 'value' transformation before a delete. The throw happens after the issues check, i.e. when the result is an unrecognized shape.","triggerScenarios":"A beforeDelete callback that returns true, a string, a promise-like value that is not an issue result, or any object without issues.","commonSituations":"Writing beforeDelete as if it could veto with a boolean (return true to proceed); porting middleware from another framework with different conventions; returning a value out of habit from beforeWrite-style hooks.","solutions":["Return nothing (undefined) from beforeDelete when the delete should proceed","Return { issues: [{ message: 'reason' }] } to block the delete","Audit beforeDelete callbacks for stray return statements"],"exampleFix":"// before\nbeforeDelete: (row) => {\n  return row.deletable // boolean: invalid\n}\n\n// after\nbeforeDelete: (row) => {\n  if (!row.deletable) {\n    return { issues: [{ message: 'Row cannot be deleted' }] }\n  }\n  // return nothing to allow the delete\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isValidBeforeDeleteResult(result: unknown): boolean {\n  return result === undefined || (typeof result === 'object' && result !== null && 'issues' in result)\n}","tryCatchPattern":"try {\n  await table.delete({ where })\n} catch (error) {\n  if (error instanceof DataTableValidationError && /beforeDelete callback result/.test(error.message)) {\n    // hook bug: audit beforeDelete return values\n  } else throw error\n}","preventionTips":["Return nothing from beforeDelete on success; use { issues } to veto","Never return booleans from beforeDelete","Review hooks after porting middleware from other frameworks"],"tags":["data-table","before-delete","lifecycle-hooks","validation"],"backgroundTag":"invalid-hook-return-shape","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}