mastra-ai/mastra · error
Notification ${input.id} was not found for thread ${input.th
Error message
Notification ${input.id} was not found for thread ${input.threadId} What it means
updateNotification looks up the notification by the (threadId, id) composite key in the in-memory map. If no record exists for that pair it throws this error instead of silently returning. This is an existence check to prevent phantom updates.
Source
Thrown at packages/core/src/notifications/storage.ts:164
const results = [...this.#notifications.values()]
.filter(record => record.status === 'pending')
.filter(record => !input.agentId || record.agentId === input.agentId)
.filter(record => !input.resourceId || record.resourceId === input.resourceId)
.filter(record => dueTime(record) <= now)
.sort((a, b) => dueTime(a) - dueTime(b) || a.updatedAt.getTime() - b.updatedAt.getTime());
return results.slice(0, input.limit ?? results.length).map(cloneRecord);
}
async getNotification(input: { threadId: string; id: string }): Promise<NotificationRecord | null> {
const record = this.#notifications.get(notificationKey(input.threadId, input.id));
if (!record) return null;
return cloneRecord(record);
}
async updateNotification(input: UpdateNotificationInput): Promise<NotificationRecord> {
const existing = this.#notifications.get(notificationKey(input.threadId, input.id));
if (!existing) {
throw new Error(`Notification ${input.id} was not found for thread ${input.threadId}`);
}
const now = new Date();
const next: NotificationRecord = {
...existing,
...(input.status ? { status: input.status, ...statusTimestamp(input.status, now) } : {}),
...(input.summary !== undefined ? { summary: input.summary } : {}),
...(input.payload !== undefined ? { payload: cloneValue(input.payload) } : {}),
...(input.attributes !== undefined ? { attributes: cloneValue(input.attributes) } : {}),
...(input.metadata !== undefined ? { metadata: cloneValue(input.metadata) } : {}),
...(input.deliverAt !== undefined ? { deliverAt: input.deliverAt ?? undefined } : {}),
...(input.summaryAt !== undefined ? { summaryAt: input.summaryAt ?? undefined } : {}),
...(input.deliveryReason !== undefined ? { deliveryReason: input.deliveryReason } : {}),
...(input.deliveryAttempts !== undefined ? { deliveryAttempts: input.deliveryAttempts } : {}),
...(input.lastDeliveryAttemptAt !== undefined ? { lastDeliveryAttemptAt: input.lastDeliveryAttemptAt } : {}),
...(input.lastDeliveryError !== undefined ? { lastDeliveryError: input.lastDeliveryError } : {}),
...(input.deliveredSignalId !== undefined ? { deliveredSignalId: input.deliveredSignalId } : {}),
...(input.summarySignalId !== undefined ? { summarySignalId: input.summarySignalId } : {}),
updatedAt: now,View on GitHub (pinned to 75dd419e61)
Solutions
- Confirm the id and threadId pair matches an existing record by listing notifications first (storage.listNotifications({ threadId }))
- Persist notifications with a durable storage adapter instead of in-memory storage if updates span process restarts
- Guard updates: fetch the record, check it exists, then update
- Verify you are not confusing threadId with resourceId in the input
Example fix
// before
await storage.updateNotification({ threadId: wrongThread, id: notificationId, status: 'seen' });
// after
const [existing] = await storage.listNotifications({ threadId: correctThread, id: notificationId });
if (existing) await storage.updateNotification({ threadId: correctThread, id: notificationId, status: 'seen' }); Defensive patterns
Strategy: try-catch
Validate before calling
const existing = await storage.listNotifications({ threadId, id });
if (!existing.length) return null; // skip update Type guard
function isNotFoundForThread(e: unknown, threadId: string, id: string): boolean {
return e instanceof Error && e.message === `Notification ${id} was not found for thread ${threadId}`;
} Try / catch
try {
await storage.updateNotification({ threadId, id, status: 'seen' });
} catch (e) {
if (isNotFoundForThread(e, threadId, id)) {
logger.warn('Notification already gone, skipping update', { threadId, id });
return;
}
throw e;
} Prevention
- Fetch-then-update to confirm existence before updating
- Use durable storage instead of in-memory storage across restarts
- Never reuse notification ids across threads
When it happens
Trigger: Calling updateNotification with an id that was never created, an id belonging to a different thread, or after the record was deleted / storage was reset (in-memory storage loses data on restart).
Common situations: Using a stale notification id from a previous session; passing the wrong threadId (e.g. swapped thread and resource ids); restarting a dev server backed by in-memory NotificationsStorage and replaying old update calls.
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
- Version-control repository not found.
- Project repository not found for this organization.
- Source-control connection not found for this organization.
- Repository not found for this organization.
- Source-control installation not found for this organization.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/9790557c565c319c.
Report an issue: GitHub.