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

  1. Confirm entityType (table name) passed to insert matches the table findById queries — they must be the same string.
  2. Verify the id generated in build() is actually being written (check the DB directly right after the insert throws).
  3. Ensure the table's primary key column is named 'id' (the adapter hardcodes .where('id', id)).
  4. Check for DB-level constraints/triggers that could reject the insert without throwing (strict-mode NOT NULL, etc.).
  5. 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

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


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/16fca543be0ce694. Report an issue: GitHub.