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 theView on GitHub (pinned to 988cbb0330)
Solutions
- Check DB health and slow-query logs for the HasTombstoneForMessage query
- Ensure the tombstone-by-message index exists and migrations are current
- Retry the write after the transient error — the source messages are re-read later
- 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
- Index tombstones by (scope, source_message_id) so the lookup stays fast
- Ensure rejectedMessageWindow isn't so large that scans time out
- Run migrations before deploying distillation workers
- Alert on repeated wrapped DB errors in the memory write path
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
- check forgotten memory: %w
- ensure memory subject: %w
- find conflicting memory: %w
- create memory item: %w
- scan for duplicate memory: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/2deb37c9d3617eea.
Report an issue: GitHub.