different-ai/openwork · error
That skill is not part of this plugin.
Error message
That skill is not part of this plugin.
What it means
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.
Source
Thrown at ee/apps/den-web/app/(den)/dashboard/_components/skill-data.tsx:103
queryFn: async (): Promise<DenSkill> => {
const encodedSkillId = encodeURIComponent(skillId);
const [{ response, payload }, membershipResult] = await Promise.all([
requestJson(`/v1/config-objects/${encodedSkillId}`, { method: "GET" }, 15000),
requestJson(`/v1/config-objects/${encodedSkillId}/plugins`, { method: "GET" }, 15000),
]);
if (!response.ok) {
throw new Error(getErrorMessage(payload, `Failed to load skill (${response.status}).`));
}
if (!membershipResult.response.ok) {
throw new Error(getErrorMessage(membershipResult.payload, `Failed to verify skill plugin (${membershipResult.response.status}).`));
}
const belongsToPlugin = isRecord(membershipResult.payload)
&& Array.isArray(membershipResult.payload.items)
&& membershipResult.payload.items.some((entry) => (
isRecord(entry) && entry.pluginId === pluginId && entry.removedAt === null
));
if (!belongsToPlugin) {
throw new Error("That skill is not part of this plugin.");
}
const skill = parseSkillResponse(payload);
if (!skill) {
throw new Error("Skill detail response was incomplete.");
}
return skill;
},
});
}
export function useCreateSkill(pluginId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (draft: SkillDraft): Promise<DenSkill> => {
const { response, payload } = await requestJson(
"/v1/config-objects",
{View on GitHub (pinned to 2b7df46e8a)
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.
Example fix
// before useSkill(routePluginId, skillId) // routePluginId from an outdated URL // after const pluginId = plugin?.id; // resolved from current plugin record if (!pluginId) return null; return useSkill(pluginId, skillId);
Defensive patterns
Strategy: type-guard
Validate before calling
function isPluginMembershipPayload(v: unknown): v is { items: { pluginId: unknown; removedAt: unknown }[] } {
return isRecord(v) && Array.isArray(v.items) && v.items.every((e) => isRecord(e));
} Type guard
function membershipHasSkill(items: unknown[], pluginId: string): boolean {
return items.some((e) => isRecord(e) && e.pluginId === pluginId && e.removedAt === null);
} Try / catch
try {
const skill = await refetchSkill(pluginId, skillId);
} catch (e) {
if (e instanceof Error && e.message === "That skill is not part of this plugin.") {
// fall back to plugin skill list or show 'skill removed' state
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- request_failed
- invalid_session_payload
- invalid_app_version_payload
- invalid_resource_snapshot_payload
- invalid_mcp_token_payload
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/89853b71169fe23b.
Report an issue: GitHub.