mastra-ai/mastra · error · HTTPException

Prompt block with id ${id} already exists

Error message

Prompt block with id ${id} already exists

What it means

The create prompt-block route enforces unique IDs: before inserting, it calls `promptBlockStore.getById(id)` and throws a 409 Conflict if a record with that ID already exists. Unlike some stores, the HTTP create path does not silently upsert — a duplicate ID is a conflict the caller must resolve.

Source

Thrown at packages/server/src/server/handlers/stored-prompt-blocks.ts:187

      const promptBlockStore = await storage.getStore('promptBlocks');
      if (!promptBlockStore) {
        throw new HTTPException(500, { message: 'Prompt blocks 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 prompt block ID from name. Please provide an explicit id.',
        });
      }

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

      await promptBlockStore.create({
        promptBlock: {
          id,
          authorId,
          metadata: scopeStoredResourceMetadata(metadata, await getStoredResourceScope(mastra, requestContext)),
          name,
          description,
          content,
          rules,
          requestContextSchema,
        },
      });

      // Return the resolved prompt block (thin record + version config)
      // Use draft status since newly created entities start as drafts
      const resolved = await promptBlockStore.getByIdResolved(id, { status: 'draft' });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Choose a different unique id (or name) for the new prompt block
  2. Check existence first via GET /stored/prompt-blocks/:id and use the update (PUT) route to modify the existing record
  3. Make import scripts idempotent by fetching/updating instead of always creating
  4. Generate ids with a unique suffix (e.g. slug + timestamp or short uuid)

Example fix

// before
await client.createStoredPromptBlock({ id: 'my-prompt', name: 'My Prompt' }) // 409 if exists
// after
const existing = await client.getStoredPromptBlock('my-prompt');
if (existing) {
  await client.updateStoredPromptBlock('my-prompt', { /* changes */ });
} else {
  await client.createStoredPromptBlock({ id: 'my-prompt', name: 'My Prompt' });
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await fetch(`${baseUrl}/api/stored/prompt-blocks/${id}`);
if (existing.ok) {
  // conflict: update instead of create
}

Try / catch

try {
  await client.createStoredPromptBlock(body);
} catch (e) {
  if (isHTTPException(e) && e.status === 409 && e.message.includes('already exists')) {
    await client.updateStoredPromptBlock(body.id, body);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /stored/prompt-blocks with an explicit `id` (or a name slugifying to an id) that already matches an existing stored prompt block in the database.

Common situations: Re-running a seed/import script without idempotency; two team members creating a block with the same name; retrying a create after a timeout when the first request actually succeeded; name collisions after slugification ('My Block' and 'my block' share the slug 'my-block').

Related errors


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