Module: ClaudeMemory::MCP::ErrorClassifier

Defined in:
lib/claude_memory/mcp/error_classifier.rb

Overview

Classifies MCP tool errors into three tiers with structured responses.

Benign: Empty results, first use, database not yet initialized.

No error surfaced — return helpful guidance instead.

Retryable: Database locked, temporary I/O, connection reset.

Tool failed but may succeed on retry.

Fatal: Schema corruption, invalid parameters, programming bugs.

Requires user intervention to resolve.

Source: supermemory src/lib/error-helpers.js:1-72

Constant Summary collapse

SEVERITY_BENIGN =
"benign"
SEVERITY_RETRYABLE =
"retryable"
SEVERITY_FATAL =
"fatal"
RETRYABLE_ERROR_NAMES =

Errors that indicate temporary/transient failures

%w[
  Sequel::DatabaseConnectionError
  Extralite::BusyError
].freeze
RETRYABLE_ERROR_CLASSES =
[
  Errno::EACCES,
  Errno::EAGAIN,
  IOError
].freeze
FATAL_ERROR_CLASSES =

Errors that indicate permanent/programming failures

[
  Errno::ENOSPC,
  Errno::EROFS,
  TypeError,
  NoMethodError,
  ArgumentError
].freeze

Class Method Summary collapse

Class Method Details

.benign_message(reason) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/claude_memory/mcp/error_classifier.rb', line 157

def benign_message(reason)
  case reason
  when :no_results
    "No matching facts found. The memory system is working but has no relevant data for this query."
  when :not_initialized
    "Memory system not yet initialized. Facts will be stored as conversations happen."
  when :empty_database
    "Database exists but contains no facts yet. Use store_extraction to add facts."
  else
    "No data available."
  end
end

.build_benign_response(reason, tool_name: nil) ⇒ Object



83
84
85
86
87
88
89
90
# File 'lib/claude_memory/mcp/error_classifier.rb', line 83

def build_benign_response(reason, tool_name: nil)
  {
    severity: SEVERITY_BENIGN,
    message: benign_message(reason),
    tool: tool_name,
    results: []
  }
end

.build_error_response(error, tool_name: nil) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/claude_memory/mcp/error_classifier.rb', line 59

def build_error_response(error, tool_name: nil)
  severity = classify(error)

  case severity
  when SEVERITY_RETRYABLE
    {
      error: "Temporary failure",
      severity: SEVERITY_RETRYABLE,
      message: retryable_message(error),
      tool: tool_name,
      retry: true
    }
  when SEVERITY_FATAL
    {
      error: "Operation failed",
      severity: SEVERITY_FATAL,
      message: fatal_message(error),
      tool: tool_name,
      retry: false,
      recommendations: fatal_recommendations(error)
    }
  end
end

.classify(error) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/claude_memory/mcp/error_classifier.rb', line 45

def classify(error)
  if retryable?(error)
    SEVERITY_RETRYABLE
  elsif fatal?(error)
    SEVERITY_FATAL
  elsif database_error?(error)
    # Generic database errors default to retryable
    SEVERITY_RETRYABLE
  else
    # Unknown errors default to fatal to surface issues
    SEVERITY_FATAL
  end
end

.database_error?(error) ⇒ Boolean

Returns:

  • (Boolean)


101
102
103
# File 'lib/claude_memory/mcp/error_classifier.rb', line 101

def database_error?(error)
  error.class.ancestors.any? { |a| a.name == "Sequel::DatabaseError" }
end

.fatal?(error) ⇒ Boolean

Returns:

  • (Boolean)


97
98
99
# File 'lib/claude_memory/mcp/error_classifier.rb', line 97

def fatal?(error)
  FATAL_ERROR_CLASSES.any? { |klass| error.is_a?(klass) }
end

.fatal_message(error) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/claude_memory/mcp/error_classifier.rb', line 122

def fatal_message(error)
  case error
  when Errno::ENOSPC
    "No disk space available for database operations."
  when Errno::EROFS
    "Database is on a read-only filesystem."
  when TypeError, NoMethodError
    "Internal error in memory system."
  when ArgumentError
    "Invalid parameters provided."
  else
    "Database error: #{error.message}"
  end
end

.fatal_recommendations(error) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/claude_memory/mcp/error_classifier.rb', line 137

def fatal_recommendations(error)
  recs = ["Run memory.check_setup to diagnose the issue"]

  case error
  when Errno::ENOSPC
    recs << "Free up disk space and retry"
  when Errno::EROFS
    recs << "Check filesystem permissions"
  else
    if error.message.include?("corrupt") || error.message.include?("malformed")
      recs << "Database may be corrupted — run: claude-memory doctor"
      recs << "If unrecoverable: claude-memory init --force"
    else
      recs << "If persistent: claude-memory doctor"
    end
  end

  recs
end

.retryable?(error) ⇒ Boolean

Returns:

  • (Boolean)


92
93
94
95
# File 'lib/claude_memory/mcp/error_classifier.rb', line 92

def retryable?(error)
  RETRYABLE_ERROR_CLASSES.any? { |klass| error.is_a?(klass) } ||
    RETRYABLE_ERROR_NAMES.any? { |name| error.class.ancestors.any? { |a| a.name == name } }
end

.retryable_message(error) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/claude_memory/mcp/error_classifier.rb', line 105

def retryable_message(error)
  case error
  when Errno::EACCES
    "Database file is temporarily inaccessible. Another process may hold the lock."
  when Errno::EAGAIN
    "Resource temporarily unavailable. Try again shortly."
  when IOError
    "I/O error during database access. Connection may have been interrupted."
  else
    if error.class.name&.include?("Busy")
      "Database is busy. Another operation is in progress."
    else
      "Temporary database error: #{error.message}"
    end
  end
end