mastra-ai/mastra · error
Skill not found: ${invocation.skillName}.
Error message
Skill not found: ${invocation.skillName}. What it means
Thrown by resolveKickoffMessage when a factory start request includes a skill-based kickoff invocation whose skill name cannot be resolved from the session workspace, or the resolved skill is not user-invocable. After an optional refresh, the workspace skills store returns nothing for `invocation.skillName`, or the skill record carries `user-invocable: false`, so building the `<skill>` kickoff message is refused rather than silently starting with a broken kickoff.
Source
Thrown at mastracode/factory/src/rules/start-coordinator.ts:76
type FactoryController = AgentController<MastraCodeState>;
type FactorySession = Awaited<ReturnType<FactoryController['createSession']>>;
function escapeSkillBoundary(value: string): string {
return value.replaceAll('</skill>', '</skill>');
}
async function resolveKickoffMessage(
session: FactorySession,
invocation: FactoryStartRequest['invocation'],
): Promise<string | null> {
if (!invocation) return null;
if (invocation.type === 'prompt') return invocation.prompt;
const skills = session.getWorkspace()?.skills;
await skills?.maybeRefresh();
const skill = await skills?.get(invocation.skillName);
if (!skill || skill['user-invocable'] === false) {
throw new Error(`Skill not found: ${invocation.skillName}.`);
}
const args = invocation.arguments.trim();
const content = `${formatSkillActivation(skill)}${args ? `\n\nARGUMENTS: ${args}` : ''}`.trim();
return `<skill name="${skill.name}">\n${escapeSkillBoundary(content)}\n</skill>`;
}
async function resolveSourceSession(
storage: SourceControlStorageHandle,
request: FactoryStartRequest,
): Promise<SourceControlSession> {
const session = await storage.sessions.getBySessionId(request.sessionId);
if (!session || session.orgId !== request.orgId || session.userId !== request.userId) {
throw new Error('Factory session not found');
}
const projectRepository = await storage.projectRepositories.get({
orgId: request.orgId,
id: session.projectRepositoryId,
});View on GitHub (pinned to 75dd419e61)
Solutions
- Verify the skill exists in the session workspace under the exact name in invocation.skillName (check the skill's frontmatter `name` matches, names are case-sensitive).
- If the skill should be user-triggerable, set or remove the `user-invocable: false` frontmatter flag in the skill's SKILL.md.
- Ensure the skill file is present in the workspace directory for this session; move/copy the skill or start the session in the correct workspace.
- If the skill is agent-only by design, change the kickoff to a 'prompt'-type invocation with inline instructions instead of a skill reference.
Example fix
// before
await factory.start({
sessionId,
kickoff: { type: 'skill', skillName: 'code-review', arguments: '' } // skill is user-invocable: false
});
// after (either fix works)
await factory.start({
sessionId,
kickoff: { type: 'skill', skillName: 'code-review', arguments: '' } // after removing `user-invocable: false` from SKILL.md
});
// or
await factory.start({
sessionId,
kickoff: { type: 'prompt', prompt: 'Run the code-review skill on the diff.' }
}); Defensive patterns
Strategy: validation
Validate before calling
const skills = session.getWorkspace()?.skills;
await skills?.maybeRefresh();
const skill = await skills?.get(invocation.skillName);
if (!skill || skill['user-invocable'] === false) {
throw new Error(`Cannot start: skill '${invocation.skillName}' is missing or not user-invocable in this workspace.`);
} Type guard
function isInvocableSkill(s: unknown): s is { name: string; 'user-invocable': true } {
return typeof s === 'object' && s !== null && 'name' in s && (s as any)['user-invocable'] !== false;
} Try / catch
try {
await factory.prepare(request);
} catch (err) {
if (err instanceof Error && err.message.startsWith('Skill not found:')) {
console.error(`Kickoff skill '${request.kickoff.skillName}' not found in workspace; check name and user-invocable flag.`);
}
throw err;
} Prevention
- Keep skill frontmatter `name` in sync wherever the skill is referenced in automations.
- List the workspace skills (and their user-invocable flags) before wiring a skill-based kickoff.
- Prefer prompt-type kickoffs for agent-only skills.
When it happens
Trigger: Calling factory start (prepare/triage/plan/first/replay paths that resolve a kickoff message) with an invocation of type 'skill' where (a) the session workspace has no skills store (skills is undefined), (b) no skill exists under invocation.skillName (typo, renamed/deleted skill, not yet synced to the workspace), or (c) the skill exists but has frontmatter `user-invocable: false` so it may only be invoked by the agent, not by a user prompt.
Common situations: Developer renamed a SKILL.md directory or name field but old automations still pass the old name; the skill lives in a different repo/directory than the session workspace so it is never indexed; the skill is intentionally internal-only (user-invocable: false) but wired into a user-facing kickoff; a stale workspace cache that maybeRefresh fails to update.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- skill_not_found
- Project path is required
- A session workspace must be a valid Workspace instance.
- MASTRA_ADD_WORKSPACE_MISSING_AGENT_METADATA
- CompositeFilesystem requires at least one mount
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/92611e58d554c3a4.
Report an issue: GitHub.