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>', '&lt;/skill&gt;');
}

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

  1. 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).
  2. If the skill should be user-triggerable, set or remove the `user-invocable: false` frontmatter flag in the skill's SKILL.md.
  3. 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.
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/92611e58d554c3a4. Report an issue: GitHub.