{"record":{"id":"8b00b966f124132e","repo":"different-ai/openwork","slug":"failed-to-delete-skill-response-status","errorCode":null,"errorMessage":"Failed to delete skill (${response.status}).","messagePattern":"Failed to delete skill \\((.+?)\\)\\.","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx","lineNumber":192,"sourceCode":"      ]);\n    },\n  });\n}\n\nexport function useDeleteSkill(pluginId: string) {\n  const queryClient = useQueryClient();\n  const { runReauthableAction } = useOrgDashboard();\n\n  return useMutation({\n    mutationFn: async (skillId: string): Promise<string> => {\n      await runReauthableAction(\"delete-skill\", async () => {\n        const { response, payload } = await requestJson(\n          `/v1/config-objects/${encodeURIComponent(skillId)}/delete`,\n          { method: \"POST\" },\n          15000,\n        );\n        if (!response.ok) {\n          throw getRequestError(payload, response, `Failed to delete skill (${response.status}).`);\n        }\n      });\n      return skillId;\n    },\n    onSuccess: async () => {\n      await queryClient.cancelQueries({ queryKey: skillQueryKeys.all });\n      queryClient.removeQueries({ queryKey: skillQueryKeys.all });\n      await queryClient.invalidateQueries({ queryKey: pluginQueryKeys.detail(pluginId) });\n    },\n  });\n}\n","sourceCodeStart":174,"sourceCodeEnd":204,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx#L174-L204","documentation":"Thrown by useDeleteSkill when POST /v1/config-objects/{skillId}/delete returns a non-ok status. Deletion is modeled as a POST to a /delete action endpoint and is wrapped in runReauthableAction so 403 reauth challenges are retried after re-authentication; any other failure surfaces this error with the server's message. The skill cache is only cleared on success.","triggerScenarios":"POST /v1/config-objects/{skillId}/delete returns 401 (expired token after runReauthableAction exhausted its re-auth path), 403 (no delete permission / org policy), 404 (skill already deleted elsewhere), 409 (skill is referenced/locked, e.g. attached to a published plugin version), 429, or 5xx. 15s timeout.","commonSituations":"Two admins delete the same skill concurrently (second gets 404); org policy forbids deleting skills in use by an active plugin; session fully expired so even reauth cannot proceed; Den server outage returns 502/503.","solutions":["Read the server message in the error to distinguish permission (403), missing (404), and conflict (409).","On 404, treat deletion as already-done: invalidate queries and remove from UI instead of surfacing an error.","If it is a ReauthRequiredError that escaped, prompt sign-in and retry the delete.","Check org role/policy — deleting may require an admin or Den-side allowlist.","Retry on 429/5xx with backoff; check Den server health for persistent 5xx."],"exampleFix":"// before\ndeleteSkill.mutate(skillId, { onError: (e) => alert(e.message) });\n// after: tolerate already-deleted (404)\ndeleteSkill.mutate(skillId, {\n  onError: (e) => {\n    if (/\\b404\\b/.test(e.message)) {\n      queryClient.invalidateQueries({ queryKey: skillQueryKeys.all });\n      return;\n    }\n    alert(e.message);\n  },\n});","handlingStrategy":"try-catch","validationCode":"if (!skillId) throw new Error(\"skillId is required to delete a skill.\");\n// optionally confirm existence first:\nconst res = await fetch(`/v1/config-objects/${encodeURIComponent(skillId)}`);\nif (res.status === 404) { /* already gone; skip delete */ }","typeGuard":"function isReauthRequiredError(e: unknown): e is ReauthRequiredError {\n  return e instanceof ReauthRequiredError;\n}","tryCatchPattern":"try {\n  await deleteSkill.mutateAsync(skillId);\n} catch (err) {\n  if (isReauthRequiredError(err)) { promptSignIn(); return; }\n  if (/\\b404\\b/.test(err.message)) { cleanupLocalState(); return; } // already deleted\n  showError(err.message);\n}","preventionTips":["Delete is already wrapped in runReauthableAction — keep it that way when refactoring.","Treat 404 as success (idempotent delete) in UI error handling.","Confirm org role allows deletion before showing the delete button.","Invalidate skill and plugin queries only after success, as the hook does.","For 429/5xx, surface a retryable error rather than a hard failure."],"tags":["http","api","skills","delete","den-web"],"backgroundTag":"http-request-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}