{"record":{"id":"c1b7ea093f23a2c2","repo":"different-ai/openwork","slug":"failed-to-create-skill-response-status","errorCode":null,"errorMessage":"Failed to create skill (${response.status}).","messagePattern":"Failed to create skill \\((.+?)\\)\\.","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx","lineNumber":128,"sourceCode":"    },\n  });\n}\n\nexport function useCreateSkill(pluginId: string) {\n  const queryClient = useQueryClient();\n\n  return useMutation({\n    mutationFn: async (draft: SkillDraft): Promise<DenSkill> => {\n      const { response, payload } = await requestJson(\n        \"/v1/config-objects\",\n        {\n          method: \"POST\",\n          body: JSON.stringify(createSkillPayload(pluginId, draft)),\n        },\n        15000,\n      );\n      if (!response.ok) {\n        throw getRequestError(payload, response, `Failed to create skill (${response.status}).`);\n      }\n      const skill = parseSkillResponse(payload);\n      if (!skill) {\n        throw new Error(\"Skill create response was incomplete.\");\n      }\n      return skill;\n    },\n    onSuccess: async () => {\n      await Promise.all([\n        queryClient.invalidateQueries({ queryKey: skillQueryKeys.all }),\n        queryClient.invalidateQueries({ queryKey: pluginQueryKeys.detail(pluginId) }),\n      ]);\n    },\n  });\n}\n\nexport function useUpdateSkill(pluginId: string) {\n  const queryClient = useQueryClient();","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx#L110-L146","documentation":"This error is thrown by useCreateSkill in skill-data.tsx when the POST to /v1/config-objects (the Den server endpoint that creates a skill config object) returns a non-ok HTTP status. getRequestError enriches the fallback message with the server's JSON error payload (payload.message/error) and upgrades 403-with-error:'reauth' responses to a ReauthRequiredError. It means the server rejected the skill creation request, not a client-side parse failure.","triggerScenarios":"POST /v1/config-objects with body {type:'skill', sourceMode:'cloud', pluginIds:[pluginId], input:{rawSourceText}} returns 400 (malformed markdown/composed skill source), 401 (expired session token), 403 (no org permission, or reauth required), 404 (pluginId does not exist), 409 (duplicate skill name in plugin), 413 (body too large), 429 (rate limit), or 5xx from an upstream Den failure. The request has a 15s timeout via requestJson.","commonSituations":"Developer creates a skill from Den Web while their session token expired; the pluginId in the URL/route is stale after the plugin was deleted; org quota or permission limits block the create; the Den server is behind a proxy that returns an HTML 502 page; validation rejects skill body content (e.g. empty name after trimming).","solutions":["Read the error message appended by getRequestError: it contains the server's message/error field which names the real cause (validation, permissions, duplicate).","If the error is a ReauthRequiredError (403 error:'reauth'), re-run inside runReauthableAction like useDeleteSkill does, or have the user sign in again.","Verify pluginId is valid and still exists: GET /v1/config-objects or the plugin query before creating.","Check the composed rawSourceText via skillSourceFromDraft(draft) — non-empty name/description/body is required by the markdown composer and server validation.","Check network tab for the actual status; 5xx/HTML pages indicate Den server/proxy problems rather than payload issues.","For 429/5xx add a retry with backoff in the mutation or let the user retry."],"exampleFix":"// before: create without reauth handling\nconst create = useCreateSkill(pluginId);\nawait create.mutateAsync(draft); // throws on 403 reauth\n// after: guard the mutation like useDeleteSkill\nconst { runReauthableAction } = useOrgDashboard();\ntry {\n  await runReauthableAction(\"create-skill\", () => create.mutateAsync(draft));\n} catch (err) {\n  if (!isReauthRequiredError(err)) reportError(err);\n}","handlingStrategy":"try-catch","validationCode":"const draft = { name: name.trim(), description: description.trim(), body: body.trim() };\nif (!draft.name) throw new Error(\"Skill name is required.\");\nif (!pluginId) throw new Error(\"A plugin must be selected before creating a skill.\");\nconst payload = createSkillPayload(pluginId, draft);\nif (JSON.stringify(payload).length > 512 * 1024) throw new Error(\"Skill is too large.\");","typeGuard":"function isReauthRequiredError(e: unknown): e is ReauthRequiredError {\n  return e instanceof ReauthRequiredError;\n}\nfunction hasServerMessage(e: unknown): e is { message: string } {\n  return e instanceof Error && e.message.length > 0;\n}","tryCatchPattern":"try {\n  const skill = await createSkill.mutateAsync(draft);\n} catch (err) {\n  if (isReauthRequiredError(err)) { promptSignIn(); return; }\n  console.error(err.message); // contains HTTP status and/or server error field\n  showError(err.message);\n}","preventionTips":["Always render the thrown error's message — getRequestError embeds the server's message/error field with the real cause.","Compose drafts through skillSourceFromDraft and keep name/body non-empty before submitting.","Wrap mutations in runReauthableAction when the endpoint can return 403 reauth.","Keep pluginId fresh from the plugin query, not from stale route state.","Log response.status server-side/client-side to disambiguate 4xx validation from 5xx outages."],"tags":["http","api","skills","den-web"],"backgroundTag":"http-request-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}