{"record":{"id":"5b72aca2b88a755f","repo":"langfuse/langfuse","slug":"conflict-5b72ac","errorCode":"CONFLICT","errorMessage":"error.message","messagePattern":"error\\.message","errorType":"error_code","errorClass":"TRPCError","httpStatus":409,"severity":"warning","filePath":"web/src/server/api/routers/dashboardWidgets.ts","lineNumber":222,"sourceCode":"    )\n    .mutation(async ({ input, ctx }) => {\n      throwIfNoProjectAccess({\n        session: ctx.session,\n        projectId: input.projectId,\n        scope: \"dashboards:CUD\",\n      });\n\n      try {\n        // Delete the widget using the DashboardService\n        await DashboardService.deleteWidget(input.widgetId, input.projectId);\n\n        return {\n          success: true,\n        };\n      } catch (error) {\n        // If the widget is still referenced in dashboards, throw a CONFLICT error\n        if (error instanceof LangfuseConflictError) {\n          throw new TRPCError({\n            code: \"CONFLICT\",\n            message: error.message,\n          });\n        }\n        throw new TRPCError({\n          code: \"INTERNAL_SERVER_ERROR\",\n          message: (error as Error)?.message,\n        });\n      }\n    }),\n});\n","sourceCodeStart":204,"sourceCodeEnd":234,"githubUrl":"https://github.com/langfuse/langfuse/blob/59d92c7cf365150d10b753b5a0d1708902a2ed60/web/src/server/api/routers/dashboardWidgets.ts#L204-L234","documentation":"CONFLICT TRPCError from the dashboardWidgets delete mutation (dashboardWidgets.ts:222). Deleting a widget that is still referenced by one or more dashboards causes the service to raise LangfuseConflictError, which is translated to a tRPC CONFLICT (HTTP 409) carrying the underlying error.message (e.g. 'Widget is still referenced in dashboards').","triggerScenarios":"Calling the widget delete mutation while at least one dashboard row still includes this widget in its widgetIds/references; typical when a widget is shared across multiple dashboards or a dashboard was created from a template that cloned the reference.","commonSituations":"Shared KPI widgets reused on several dashboards; importing template dashboards that reference existing widgets; stale dashboard rows created before cascade cleanup was added.","solutions":["Remove the widget from every dashboard that references it (edit each dashboard's widget list) before deleting it","Use the API response to identify which dashboards still reference the widget, then unlink them","If the UI should allow force-delete, first detach references in one transaction then delete the widget","Catch CONFLICT client-side and prompt the user to unlink dashboards"],"exampleFix":"// before\nawait api.dashboardWidgets.delete.mutate({ projectId, widgetId });\n\n// after\ntry {\n  await api.dashboardWidgets.delete.mutate({ projectId, widgetId });\n} catch (e) {\n  if (e?.data?.code === 'CONFLICT') {\n    showUnlinkDashboardsPrompt(e.message);\n    return;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"const dashboards = await api.dashboards.list.query({ projectId });\nconst referencing = dashboards.filter((d) => d.widgets.some((w) => w.id === widgetId));\nif (referencing.length > 0) throw new Error('unlink dashboards first');","typeGuard":"null","tryCatchPattern":"try {\n  await api.dashboardWidgets.delete.mutate({ projectId, widgetId });\n} catch (e) {\n  if (e?.data?.code === 'CONFLICT') return promptUnlink(e.message);\n  throw e;\n}","preventionTips":["Unlink a widget from all dashboards before deleting it","Show referencing dashboards in the delete confirmation UI","Handle 409 CONFLICT distinctly from 404/500 in clients"],"tags":["trpc","conflict","dashboards","referential-integrity"],"backgroundTag":"foreign-key-constraint-conflict","analyzedSha":"59d92c7cf365150d10b753b5a0d1708902a2ed60","analyzedAt":"2026-08-27T22:22:00.402Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}