ory/hydra · warning
message not found
Error message
message not found
What it means
DeleteOne on the in-memory mailhog store returns "message not found" when the given message id is absent from memory.MessageIDIndex. The delete path looks up the index position first and fails fast if the id was never stored or was already deleted.
Source
Thrown at oryx/mailhog/memory.go:182
for i := start; i > end; i-- {
//for _, m := range memory.MessageIndex[start:end] {
messages = append(messages, *memory.Messages[i])
}
msgs := data.Messages(messages)
return &msgs, nil
}
// DeleteOne deletes an individual message by storage ID
func (memory *InMemory) DeleteOne(id string) error {
memory.mu.Lock()
defer memory.mu.Unlock()
var index int
var ok bool
if index, ok = memory.MessageIDIndex[id]; !ok && true {
return errors.New("message not found")
}
delete(memory.MessageIDIndex, id)
for k, v := range memory.MessageIDIndex {
if v > index {
memory.MessageIDIndex[k] = v - 1
}
}
memory.Messages = append(memory.Messages[:index], memory.Messages[index+1:]...)
return nil
}
// DeleteAll deletes all in memory messages
func (memory *InMemory) DeleteAll() error {
memory.mu.Lock()
defer memory.mu.Unlock()
memory.Messages = make([]*data.Message, 0)
memory.MessageIDIndex = make(map[string]int)View on GitHub (pinned to 4174065ffb)
Solutions
- Check the id against the current message list (List/Search) before deleting.
- Handle the error idempotently: treat "message not found" as success for DELETE semantics if your API allows it.
- Avoid holding ids across store restarts; re-fetch the list after restart since the memory store resets.
- Guard against concurrent deletes (e.g. two goroutines deleting the same id) with application-level dedup.
Example fix
// before
if err := store.DeleteOne(ctx, msgID); err != nil { return err }
// after
if err := store.DeleteOne(ctx, msgID); err != nil && err.Error() != "message not found" {
return err
} // treat already-deleted as idempotent success Defensive patterns
Strategy: try-catch
Validate before calling
// verify the message exists before deleting
ids, _ := store.List(ctx)
exists := false
for _, m := range ids {
if m.ID == id { exists = true; break }
}
if !exists { return nil } // nothing to delete, skip call Try / catch
err := store.DeleteOne(ctx, id)
if err != nil && strings.Contains(err.Error(), "message not found") {
// idempotent DELETE: already gone, treat as success
return nil
} else if err != nil {
return err
} Prevention
- Never reuse message ids across process restarts of the memory store
- Make DELETE endpoints idempotent by swallowing not-found errors
- Re-fetch the message list after mutations instead of caching stale ids
- Guard against duplicate concurrent deletes of the same id
When it happens
Trigger: Calling memory.DeleteOne(ctx, id) with an id that does not exist in MessageIDIndex — e.g. an id from a restarted/reseeded store, an already-deleted message, or a client-supplied id that was never valid.
Common situations: Double DELETE calls from a UI or test, stale message list rendered before another client deleted the message, ids issued by a previous process lifetime (memory store is not persistent).
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.
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/11b6347a0e3b5760.
Report an issue: GitHub.