Tencent/WeKnora · error

check forgotten source: %w

Error message

check forgotten source: %w

What it means

The second tombstone check in write(): when an extracted memory has a SourceMessageID, HasTombstoneForMessage checks whether the user already rejected a memory derived from that message. A repo failure here aborts the write with this wrapped error, preserving the invariant that rejected messages never regenerate memories.

Source

Thrown at internal/application/service/memory/service.go:337

	}

	// Something the user deliberately forgot must not come back the next time
	// distillation reads the message it came from. Two checks, because the
	// re-derived statement is usually worded slightly differently and so does
	// not hash the same: the exact fingerprint, and whether the message it came
	// from already produced a memory the user rejected.
	forgotten, err := s.repo.HasTombstone(ctx, scope, types.MemoryFingerprint(content))
	if err != nil {
		return nil, fmt.Errorf("check forgotten memory: %w", err)
	}
	if !forgotten && item.SourceMessageID != "" && item.Origin == types.MemoryOriginExtracted {
		// Only the background path is gated this way. An explicit "remember
		// this" is the user asking again, and must always win.
		forgotten, err = s.repo.HasTombstoneForMessage(
			ctx, scope, item.SourceMessageID, rejectedMessageWindow,
		)
		if err != nil {
			return nil, fmt.Errorf("check forgotten source: %w", err)
		}
	}
	if forgotten {
		logger.Infof(ctx, "memory: skipped a statement the user previously deleted")
		return nil, ErrPreviouslyForgotten
	}
	if _, err := s.repo.EnsureSubject(ctx, scope); err != nil {
		return nil, fmt.Errorf("ensure memory subject: %w", err)
	}

	topic := types.SanitizeMemoryTopic(item.Topic)
	normalizedKey := types.MemoryItemKey(topic, content)
	existing, err := s.repo.FindActiveByKey(ctx, scope, normalizedKey)
	if err != nil {
		return nil, fmt.Errorf("find conflicting memory: %w", err)
	}
	if existing != nil && types.SanitizeMemoryContent(existing.Content) == content {
		// Same statement about the same topic: nothing changed, so keep the

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Check DB health and slow-query logs for the HasTombstoneForMessage query
  2. Ensure the tombstone-by-message index exists and migrations are current
  3. Retry the write after the transient error — the source messages are re-read later
  4. Tune rejectedMessageWindow if scans are timing out on large windows

Example fix

null
Defensive patterns

Strategy: retry

Type guard

func isForgottenSourceErr(err error) bool {
	return err != nil && strings.Contains(err.Error(), "check forgotten source")
}

Try / catch

item, err := memSvc.CreateItem(ctx, scope, item)
if err != nil {
	if isForgottenSourceErr(err) {
		return retryWithBackoff(ctx, 3, call) // aborted before write; safe to retry
	}
	return err
}

Prevention

When it happens

Trigger: write() called with item.Origin == extracted and non-empty SourceMessageID, and repo.HasTombstoneForMessage returns an error (DB failure, timeout, canceled context).

Common situations: DB outage during background distillation; missing index/migration for message-scoped tombstone lookups; slow query timing out under large rejectedMessageWindow scans.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/2deb37c9d3617eea. Report an issue: GitHub.