thedotmack/claude-mem · error

Invalid CLAUDE_MEM_REDIS_MODE=${value}; expected external, m

Error message

Invalid CLAUDE_MEM_REDIS_MODE=${value}; expected external, managed, or docker

What it means

Thrown by normalizeRedisMode() when CLAUDE_MEM_REDIS_MODE (trimmed, lowercased) is not one of 'external', 'managed', or 'docker'. This classifies how Redis is provisioned for the bullmq queue. An empty/unset value also throws here, so the mode must be supplied whenever this function is reached.

Source

Thrown at src/server/queue/redis-config.ts:68

function getQueueSetting(key: keyof SettingsDefaults): string {
  if (process.env[key] !== undefined) {
    return process.env[key]!;
  }
  if (existsSync(USER_SETTINGS_PATH)) {
    const value = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH, false)[key];
    if (value !== undefined) {
      return value;
    }
  }
  return SettingsDefaultsManager.get(key);
}

function normalizeRedisMode(value: string): RedisMode {
  const normalized = value.trim().toLowerCase();
  if (normalized === 'external' || normalized === 'managed' || normalized === 'docker') {
    return normalized;
  }
  throw new Error(`Invalid CLAUDE_MEM_REDIS_MODE=${value}; expected external, managed, or docker`);
}

function parseRedisPort(value: string): number {
  const port = Number.parseInt(value, 10);
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
    throw new Error(`Invalid CLAUDE_MEM_REDIS_PORT=${value}; expected a TCP port`);
  }
  return port;
}

function sanitizePrefix(value: string): string {
  return (value.trim() || 'claude_mem').replace(/[^a-zA-Z0-9_-]/g, '_');
}

function connectionFromHost(host: string, port: number): RedisOptions {
  return {
    host,
    port,

View on GitHub (pinned to d768ba3643)

Solutions

  1. Set CLAUDE_MEM_REDIS_MODE to 'external' (your own Redis), 'managed' (a managed Redis service), or 'docker' (Redis in the Docker stack).
  2. Pick the value that matches your actual deployment topology.
  3. If you don't use bullmq, ensure CLAUDE_MEM_QUEUE_ENGINE is sqlite so this path isn't hit.

Example fix

// before: export CLAUDE_MEM_REDIS_MODE=local
// after (self-hosted Redis): export CLAUDE_MEM_REDIS_MODE=external
Defensive patterns

Strategy: validation

Validate before calling

const VALID_REDIS_MODES = new Set(['external', 'managed', 'docker']);
function resolveRedisMode(): 'external' | 'managed' | 'docker' {
  const v = (process.env.CLAUDE_MEM_REDIS_MODE ?? '').trim().toLowerCase();
  if (!VALID_REDIS_MODES.has(v)) {
    throw new Error('Set CLAUDE_MEM_REDIS_MODE to external, managed, or docker');
  }
  return v as 'external' | 'managed' | 'docker';
}

Type guard

function isRedisMode(v: string): v is 'external' | 'managed' | 'docker' {
  return v === 'external' || v === 'managed' || v === 'docker';
}

Try / catch

try {
  mode = normalizeRedisMode(getQueueSetting('CLAUDE_MEM_REDIS_MODE'));
} catch (error) {
  console.error((error as Error).message);
  process.exit(1);
}

Prevention

When it happens

Trigger: CLAUDE_MEM_REDIS_MODE is set to an invalid string (e.g. 'local', 'self-hosted', 'cloud'), or the function is called without the variable set.

Common situations: Operator invents a mode name. A stale config. The variable was expected to be optional but this code path requires it.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/2e8f960491676741. Report an issue: GitHub.