{"record":{"id":"217b4d509db56cf2","repo":"different-ai/openwork","slug":"skill-update-response-was-incomplete","errorCode":null,"errorMessage":"Skill update response was incomplete.","messagePattern":"Skill update response was incomplete\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx","lineNumber":166,"sourceCode":"  return useMutation({\n    mutationFn: async (input: { skillId: string; draft: SkillDraft }): Promise<DenSkill> => {\n      const { response, payload } = await requestJson(\n        `/v1/config-objects/${encodeURIComponent(input.skillId)}/versions`,\n        {\n          method: \"POST\",\n          body: JSON.stringify({\n            input: { rawSourceText: skillSourceFromDraft(input.draft) },\n            reason: \"Updated from Den Web\",\n          }),\n        },\n        15000,\n      );\n      if (!response.ok) {\n        throw getRequestError(payload, response, `Failed to save skill (${response.status}).`);\n      }\n      const skill = parseSkillResponse(payload);\n      if (!skill) {\n        throw new Error(\"Skill update 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 useDeleteSkill(pluginId: string) {\n  const queryClient = useQueryClient();\n  const { runReauthableAction } = useOrgDashboard();\n\n  return useMutation({\n    mutationFn: async (skillId: string): Promise<string> => {","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx#L148-L184","documentation":"useUpdateSkill PUTs skill changes and, on a 2xx response, parses the body with parseSkillResponse. When the returned skill object doesn't match the expected shape (parser returns null), it throws 'Skill update response was incomplete.' The update itself likely persisted; only the response validation failed.","triggerScenarios":"PUT skill detail returns ok but body is missing required skill fields or is not a record, so parseSkillResponse returns null.","commonSituations":"Server/client schema mismatch after a deploy; endpoint returns an empty body on save; API returns an alternate shape (e.g. wrapped in {data: ...}) the parser doesn't recognize.","solutions":["Inspect the actual PUT response body and compare against parseSkillResponse's expectations.","Update the parser or the API so the save response includes all required fields.","Verify persistence with a refetch of useSkill; if the data is correct, the failure is response-shape only.","Clear stale react-query cache after fixing so the UI refetches a well-formed detail."],"exampleFix":"// before\nconst skill = parseSkillResponse(payload);\nif (!skill) throw new Error(\"Skill update response was incomplete.\");\n// after\nconst skill = parseSkillResponse(payload) ?? parseSkillResponse((payload as {data?: unknown})?.data);\nif (!skill) throw new Error(\"Skill update response was incomplete.\");","handlingStrategy":"validation","validationCode":"function looksLikeUpdatedSkill(v: unknown): boolean {\n  return isRecord(v) && typeof v.id === \"string\" && typeof v.name === \"string\";\n}","typeGuard":"function isSkill(v: unknown): v is Skill {\n  return isRecord(v) && typeof v.id === \"string\" && typeof v.name === \"string\";\n}","tryCatchPattern":"try {\n  await updateSkill(input);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Skill update response was incomplete.\") {\n    // update likely persisted; refetch detail to confirm instead of re-saving blindly\n    await queryClient.invalidateQueries({ queryKey: skillQueryKeys.all });\n  } else throw e;\n}","preventionTips":["Confirm persistence via refetch instead of retrying the PUT","Assert response shape in API contract tests","Handle empty 200 bodies from proxies explicitly"],"tags":["api","mutation","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}