TryGhost/Ghost · error · Error
${entityType} with id ${id} not found
Error message
${entityType} with id ${id} not found What it means
Thrown by KnexPersistenceAdapter.findById() when no row matches the id in the table. Because insert() and update() both call findById() to return the resulting record, this error commonly surfaces as a follow-up: the row was written but can't be read back. Distinct from a query error — the query succeeded, the row just isn't there.
Source
Thrown at e2e/data-factory/persistence/adapters/knex.ts:58
}
async deleteMany(entityType: string, ids: string[]): Promise<void> {
if (ids.length === 0) {
return;
}
await this.db(entityType)
.whereIn('id', ids)
.del();
}
async findById<T>(entityType: string, id: string): Promise<T> {
const result = await this.db(entityType)
.where('id', id)
.first();
if (!result) {
throw new Error(`${entityType} with id ${id} not found`);
}
return result;
}
async findMany<T>(entityType: string, query?: Record<string, unknown>): Promise<T[]> {
let queryBuilder = this.db(entityType);
if (query) {
queryBuilder = queryBuilder.where(query);
}
return await queryBuilder.select();
}
}
View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Confirm entityType (table name) passed to insert matches the table findById queries — they must be the same string.
- Verify the id generated in build() is actually being written (check the DB directly right after the insert throws).
- Ensure the table's primary key column is named 'id' (the adapter hardcodes .where('id', id)).
- Check for DB-level constraints/triggers that could reject the insert without throwing (strict-mode NOT NULL, etc.).
- Rule out cross-worker interference by running the failing test in isolation (pnpm test path/to/test.ts).
Defensive patterns
Strategy: validation
Validate before calling
// Verify the row exists right after insert, with a clear assertion
async function insertAndVerify(adapter, entityType, data) {
const inserted = await adapter.insert(entityType, data);
const found = await adapter.findById(entityType, data.id);
if (!found) throw new Error(`Insert of ${entityType} silently failed — row not readable by id ${data.id}`);
return found;
} Type guard
function hasIdField(data) {
return data != null && data.id != null;
} Try / catch
try {
return await adapter.findById(entityType, id);
} catch (err) {
if (/with id .* not found/i.test(err.message)) {
// distinguish post-insert-not-readable (likely a write problem) from a genuine lookup miss
console.error('Row not found after write — check table name, id column, and DB constraints:', err.message);
}
throw err;
} Prevention
- Use the same entityType (table name) for insert and findById.
- Ensure the table's primary key column is named 'id'.
- Check DB strict-mode constraints that could silently reject inserts.
When it happens
Trigger: After insert(): the row didn't actually persist (transaction rolled back, DB connection issue) or the id passed doesn't match what was inserted. After update(): the id never existed or the update's WHERE clause matched nothing. Direct findById(): querying a non-existent or already-deleted record.
Common situations: Knex adapter's insert wrote to a different table than expected (entityType mismatch); the id field in the data doesn't match the table's actual primary key column; a DB-level trigger rejected the insert silently; the row was deleted by another worker between insert and findById; MySQL strict mode rejected a column so the insert partially failed.
Related errors
- Cannot insert without an id field
- ${entityType} with id ${id} not found
- Cannot create without a persistence adapter. Use buildMany()
- Failed to create ${entityType}: ${response.status()} ${error
- Failed to find ${entityType}: ${response.status()}
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/16fca543be0ce694.
Report an issue: GitHub.