{"record":{"id":"89853b71169fe23b","repo":"different-ai/openwork","slug":"that-skill-is-not-part-of-this-plugin","errorCode":null,"errorMessage":"That skill is not part of this plugin.","messagePattern":"That skill is not part of this plugin\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx","lineNumber":103,"sourceCode":"    queryFn: async (): Promise<DenSkill> => {\n      const encodedSkillId = encodeURIComponent(skillId);\n      const [{ response, payload }, membershipResult] = await Promise.all([\n        requestJson(`/v1/config-objects/${encodedSkillId}`, { method: \"GET\" }, 15000),\n        requestJson(`/v1/config-objects/${encodedSkillId}/plugins`, { method: \"GET\" }, 15000),\n      ]);\n      if (!response.ok) {\n        throw new Error(getErrorMessage(payload, `Failed to load skill (${response.status}).`));\n      }\n      if (!membershipResult.response.ok) {\n        throw new Error(getErrorMessage(membershipResult.payload, `Failed to verify skill plugin (${membershipResult.response.status}).`));\n      }\n      const belongsToPlugin = isRecord(membershipResult.payload)\n        && Array.isArray(membershipResult.payload.items)\n        && membershipResult.payload.items.some((entry) => (\n          isRecord(entry) && entry.pluginId === pluginId && entry.removedAt === null\n        ));\n      if (!belongsToPlugin) {\n        throw new Error(\"That skill is not part of this plugin.\");\n      }\n      const skill = parseSkillResponse(payload);\n      if (!skill) {\n        throw new Error(\"Skill detail response was incomplete.\");\n      }\n      return skill;\n    },\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        {","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx#L85-L121","documentation":"useSkill fetches a skill's detail from the Den API and verifies via a membership lookup that the skill actually belongs to the requested plugin (entry.pluginId matches and removedAt === null). If the plugin-membership items array does not contain such an entry, the hook throws 'That skill is not part of this plugin.' to prevent rendering skills detached from their plugin context.","triggerScenarios":"Calling useSkill(pluginId, skillId) where the GET membership payload's items array contains no record with entry.pluginId === pluginId and entry.removedAt === null — e.g. the skill was removed from the plugin, the wrong pluginId is passed, or the membership endpoint returns an empty/unrelated items list.","commonSituations":"A skill was detached or removed from a plugin (removedAt set) but a cached dashboard link still points at the old plugin; a copy/paste or route bug passes a different plugin's ID; the membership list hasn't propagated after just installing the skill.","solutions":["Verify the pluginId argument matches the plugin that actually contains the skill (check plugin dashboard/route params).","Re-install or re-attach the skill to the plugin so a membership row with removedAt === null exists.","Refetch after fixing: invalidate the query / reload so a stale membership response isn't reused.","If the skill was intentionally removed, update the UI entry point (link/menu) to stop requesting it under this plugin."],"exampleFix":"// before\nuseSkill(routePluginId, skillId) // routePluginId from an outdated URL\n// after\nconst pluginId = plugin?.id; // resolved from current plugin record\nif (!pluginId) return null;\nreturn useSkill(pluginId, skillId);","handlingStrategy":"type-guard","validationCode":"function isPluginMembershipPayload(v: unknown): v is { items: { pluginId: unknown; removedAt: unknown }[] } {\n  return isRecord(v) && Array.isArray(v.items) && v.items.every((e) => isRecord(e));\n}","typeGuard":"function membershipHasSkill(items: unknown[], pluginId: string): boolean {\n  return items.some((e) => isRecord(e) && e.pluginId === pluginId && e.removedAt === null);\n}","tryCatchPattern":"try {\n  const skill = await refetchSkill(pluginId, skillId);\n} catch (e) {\n  if (e instanceof Error && e.message === \"That skill is not part of this plugin.\") {\n    // fall back to plugin skill list or show 'skill removed' state\n  } else throw e;\n}","preventionTips":["Always derive pluginId from the same source that rendered the skill link","Refetch membership after install/remove mutations instead of trusting cache","Treat removedAt !== null entries as removed in UI logic"],"tags":["api","authorization","stale-data"],"backgroundTag":"skill-not-in-plugin","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}