{"record":{"id":"86052c1ef65738e8","repo":"TryGhost/Ghost","slug":"failed-to-delete-entitytype-response-status","errorCode":null,"errorMessage":"Failed to delete ${entityType}: ${response.status()}","messagePattern":"Failed to delete (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"e2e/data-factory/persistence/adapters/api.ts","lineNumber":88,"sourceCode":"        const existing = await this.findById<T>(entityType, id);\n\n        const response = await this.httpClient.put(this.buildUrl(id), {\n            data: this.transformRequest({...existing, ...data} as unknown as TRequest)\n        });\n\n        if (!response.ok()) {\n            throw new Error(`Failed to update ${entityType}: ${response.status()}`);\n        }\n\n        const body = await response.json() as TResponse;\n        return this.transformResponse(body) as T;\n    }\n\n    async delete(entityType: string, id: string): Promise<void> {\n        const response = await this.httpClient.delete(this.buildUrl(id));\n\n        if (!response.ok() && response.status() !== 404) {\n            throw new Error(`Failed to delete ${entityType}: ${response.status()}`);\n        }\n    }\n}\n","sourceCodeStart":70,"sourceCodeEnd":92,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/e2e/data-factory/persistence/adapters/api.ts#L70-L92","documentation":"Thrown by ApiPersistenceAdapter.delete() when the DELETE returns non-ok AND the status is not 404. The adapter deliberately treats 404 as success (idempotent delete — the record is gone either way), so this throw fires only on auth failures (401/403), server errors (500), or conflicts (e.g. the server refuses to delete a referenced entity).","triggerScenarios":"The test role lacks delete permission (403); the server refuses deletion because the entity is still referenced (e.g. deleting a tier that has subscriptions — a 4xx/5xx business rule); the Ghost server errored (500); authentication not propagated.","commonSituations":"Running cleanup of test data without admin privileges; deleting a parent entity whose children still reference it; the dev server errored mid-delete; the HTTP client lost its session between create and teardown.","solutions":["Inspect the status code: 403 → grant delete permission to the test role; 409/422 → remove referencing entities first; 500 → Ghost server log.","Ensure teardown runs with an authenticated admin client.","If the entity is referenced, delete dependents first or use the cascade the Admin API provides.","Remember 404 is already tolerated — a repeat delete after removal won't throw this.","Run cleanup in afterEach/beforeAll hooks with the same auth context used for creation."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Idempotent delete helper — 404 is already tolerated by the adapter, but wrap for other transient failures\nasync function safeDelete(adapter, entityType, id) {\n    try {\n        await adapter.delete(entityType, id);\n    } catch (err) {\n        if (/Failed to delete .*: (403|409|500)/.test(err.message)) throw err;\n        // tolerate other statuses in teardown\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    await factory.delete(id);\n} catch (err) {\n    const status = /Failed to delete .*: (\\d+)/.exec(err.message)?.[1];\n    if (status === '403') console.warn('No delete permission for', id);\n    else if (status === '409' || status === '422') console.warn('Entity still referenced, skipping delete', id);\n    else throw err;\n}","preventionTips":["Run teardown with an admin-authenticated client.","Delete dependents before parents to avoid business-rule conflicts.","Remember the adapter already treats 404 as success — repeated cleanup is safe."],"tags":["e2e","data-factory","api-adapter","teardown","permissions"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}