mastra-ai/mastra · error · Error
Thread with id ${newThreadId} already exists
Error message
Thread with id ${newThreadId} already exists What it means
cloneThread throws this error when the target thread id (the provided newThreadId, or a generated one in the astronomically unlikely UUID collision case) already exists in the thread map. Cloning must produce a new unique thread, so an existing target id is treated as a conflict. Only applies when newThreadId is explicitly supplied or a UUID collides.
Source
Thrown at packages/core/src/storage/domains/memory/inmemory.ts:666
this.db.resources.set(resourceId, resource);
return resource;
}
async cloneThread(args: StorageCloneThreadInput): Promise<StorageCloneThreadOutput> {
const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
// Get the source thread
const sourceThread = this.db.threads.get(sourceThreadId);
if (!sourceThread) {
throw new Error(`Source thread with id ${sourceThreadId} not found`);
}
// Use provided ID or generate a new one
const newThreadId = providedThreadId || crypto.randomUUID();
// Check if the new thread ID already exists
if (this.db.threads.has(newThreadId)) {
throw new Error(`Thread with id ${newThreadId} already exists`);
}
// Get messages from the source thread
let sourceMessages = Array.from(this.db.messages.values())
.filter((msg: StorageMessageType) => msg.thread_id === sourceThreadId)
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
// Apply message filters if provided
if (options?.messageFilter) {
const { startDate, endDate, messageIds } = options.messageFilter;
if (messageIds && messageIds.length > 0) {
const messageIdSet = new Set(messageIds);
sourceMessages = sourceMessages.filter(msg => messageIdSet.has(msg.id));
}
if (startDate) {
sourceMessages = sourceMessages.filter(msg => new Date(msg.createdAt) >= startDate);View on GitHub (pinned to 75dd419e61)
Solutions
- Omit newThreadId and let cloneThread generate a fresh crypto.randomUUID().
- Check existence first: if (await storage.getThreadById({ threadId: newThreadId })) pick another id.
- Treat the error as an idempotent success if the prior clone is known to have completed, or catch and retry with a new id.
Example fix
// before
await storage.cloneThread({ sourceThreadId, newThreadId: fixedId });
// after
const exists = await storage.getThreadById({ threadId: fixedId });
if (!exists) {
await storage.cloneThread({ sourceThreadId });
} Defensive patterns
Strategy: try-catch
Validate before calling
const existing = await storage.getThreadById({ threadId: newThreadId });
if (existing) {
throw new Error(`Thread ${newThreadId} already exists`);
} Try / catch
try {
await storage.cloneThread({ sourceThreadId, newThreadId });
} catch (e) {
if (e instanceof Error && e.message.includes('already exists')) {
// treat as idempotent success or retry with a generated id
await storage.cloneThread({ sourceThreadId });
return;
}
throw e;
} Prevention
- Omit newThreadId to let the library generate a collision-proof UUID.
- Use deterministic suffixes (e.g. `${sourceId}-copy-${Date.now()}`) for user-visible duplicate names.
- Make clone retries idempotent by checking whether the target id was already created.
When it happens
Trigger: Calling cloneThread({ sourceThreadId, newThreadId: 'existing-id' }) where existing-id already names a thread in the store; retrying a clone that already succeeded with the same explicit id; concurrent clones racing with the same newThreadId.
Common situations: Re-running an idempotency-broken retry after a timeout; two users/requests cloning into the same target id; seeding scripts executed twice with fixed ids; omitting newThreadId intentionally to auto-generate (the fix for most cases).
Related errors
- EXPERIMENT_ID_CONFLICT
- SCHEDULES_ID_EXISTS
- Agent with id ${input.agent.id} already exists
- Version number ${input.versionNumber} already exists for age
- DATASET_ID_CONFLICT
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/905a69d740d8041e.
Report an issue: GitHub.