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

  1. Confirm the id and threadId pair matches an existing record by listing notifications first (storage.listNotifications({ threadId }))
  2. Persist notifications with a durable storage adapter instead of in-memory storage if updates span process restarts
  3. Guard updates: fetch the record, check it exists, then update
  4. 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

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


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