mastra-ai/mastra · error · HTTPException

Agent with id already exists

Error message

Agent with id ${id} already exists

What it means

Thrown with HTTP 409 (Conflict) when creating a stored agent whose derived or provided `id` already exists in the agents store — `agentsStore.getById(id)` returned an existing record. Agent IDs are unique keys, so the create is refused to avoid silently overwriting an existing agent.

Solutions

  1. Check existence first with GET the agent by ID and use the update endpoint instead of create if it exists
  2. Provide a different explicit `id` for the new agent
  3. Rename the agent so the derived slug is unique
  4. Make create scripts idempotent: catch 409 and treat as 'already created' or upsert via update

Example fix

// before
await createStoredAgent({ id: 'support-bot', name: 'Support Bot' }); // 409 on rerun

// after
const existing = await getStoredAgent('support-bot');
if (existing) {
  await updateStoredAgent('support-bot', { instructions });
} else {
  await createStoredAgent({ id: 'support-bot', name: 'Support Bot' });
}
Defensive patterns

Strategy: validation

Validate before calling

const existing = await getStoredAgent(id).catch(() => null);
if (existing) {
  return updateStoredAgent(id, patch); // upsert-style
}

Type guard

null

Try / catch

try {
  await createStoredAgent(input);
} catch (e) {
  if (isHttpError(e) && e.status === 409) {
    return updateStoredAgent(input.id, input); // treat as upsert
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /api/agents where the explicit `id` in the body, or the slug derived from `name`, matches an already-stored agent (draft or published).

Common situations: Re-running a seed/bootstrap script that creates agents with fixed IDs; two users independently naming agents the same thing; retrying a create after a network timeout when the first request actually succeeded; an ID derived from a very generic agent name like 'assistant'.

Related errors


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

Appendix: source

Thrown at packages/server/src/server/handlers/stored-agents.ts:606

      const agentsStore = await storage.getStore('agents');
      if (!agentsStore) {
        throw new HTTPException(500, { message: 'Agents storage domain is not available' });
      }

      // Derive ID from name if not explicitly provided
      const id = providedId || toSlug(name);

      if (!id) {
        throw new HTTPException(400, {
          message: 'Could not derive agent ID from name. Please provide an explicit id.',
        });
      }

      // Check if agent with this ID already exists
      const existing = await agentsStore.getById(id);
      if (existing) {
        throw new HTTPException(409, { message: `Agent with id ${id} already exists` });
      }

      // Force authorId from the authenticated caller; ignore any body-provided value.
      // No owner = always public (no auth / no user context).
      // With an owner, respect the client's choice, defaulting to 'private'.
      const authorId = getCallerAuthorId(requestContext) ?? undefined;
      const visibility = authorId ? (bodyVisibility ?? 'private') : 'public';

      // Reject oversized avatar images before writing to storage.
      validateMetadataAvatarUrl(metadata);

      // Model policy enforcement is intentionally not done on save: each UI
      // surface gates its own model picker via ModelPolicyProvider, and the
      // policy is surface-scoped (builder vs editor). Re-introducing a single
      // server-side check here would either over-enforce on the editor or
      // under-enforce on the builder until per-surface enforcement lands.

      const resolvedBrowser = await resolveBrowserField(browser, mastra);

View on GitHub (pinned to 75dd419e61)