thedotmack/claude-mem · error · Error

SyncClient requires a non-empty hubUrl (CLAUDE_MEM_CLOUD_SYN

Error message

SyncClient requires a non-empty hubUrl (CLAUDE_MEM_CLOUD_SYNC_HUB_URL)

What it means

Thrown by the SyncClient constructor when the hubUrl option (from CLAUDE_MEM_CLOUD_SYNC_HUB_URL) is empty or whitespace-only after trimming and trailing-slash stripping. SyncClient refuses to start without a hub endpoint because pulling without one would silently no-op. This is a fail-closed construction guard.

Source

Thrown at src/services/sync/SyncClient.ts:256

  private backoffMs = 0;
  private failStreak = 0;
  private failCursor: string | null = null;

  // Advisory socket state (all of it disposable — prime directive #2).
  private socket: SyncSocketLike | null = null;
  private socketLive = false;
  private wsAttempts = 0;
  private pingTimer: ReturnType<typeof setInterval> | null = null;
  private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
  /** True while the hub says X-Sync-Mode: poll (kill switch tripped). */
  private pollModeOnly = false;
  /** True while the pull loop is suspended (socket torn down with it). */
  private suspended = false;

  constructor(apply: SyncApply, options: SyncClientOptions) {
    const hubUrl = (options.hubUrl ?? '').trim().replace(/\/+$/, '');
    if (!hubUrl) {
      throw new Error('SyncClient requires a non-empty hubUrl (CLAUDE_MEM_CLOUD_SYNC_HUB_URL)');
    }
    if (!options.deviceId) {
      // Same fail-closed posture as CloudSync/SyncApply: pulling without an
      // identity would mis-classify our own echoes.
      throw new Error('SyncClient requires a non-empty deviceId (use the CloudSync-resolved id)');
    }
    this.apply = apply;
    this.hubUrl = hubUrl;
    this.token = options.token ?? '';
    this.userId = options.userId ?? '';
    this.deviceId = options.deviceId;
    this.deviceName = (options.deviceName ?? '').trim().slice(0, 80);
    this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
    this.activePollMs = options.activePollMs ?? 30_000;
    this.idlePollMs = options.idlePollMs ?? 300_000;
    this.suspendAfterMs = options.suspendAfterMs ?? 3_600_000;
    this.pageLimit = options.pageLimit ?? 500;
    this.maxPagesPerCycle = options.maxPagesPerCycle ?? 40;

View on GitHub (pinned to d768ba3643)

Solutions

  1. Set CLAUDE_MEM_CLOUD_SYNC_HUB_URL to the hub base URL (e.g. https://sync.example.com) in your shell config or ~/.claude-mem/settings.json.
  2. If cloud sync is intentionally disabled, unset the enable flag (e.g. CLAUDE_MEM_CLOUD_SYNC_ENABLED=false) so SyncClient is not constructed.
  3. Confirm the env var is exported in the process that runs the worker (print process.env.CLAUDE_MEM_CLOUD_SYNC_HUB_URL at startup).
  4. Check for trailing whitespace or a missing export in dotfiles.

Example fix

# before: CLAUDE_MEM_CLOUD_SYNC_ENABLED=true  (hub url missing -> throws)
# after:  export CLAUDE_MEM_CLOUD_SYNC_HUB_URL=https://sync.example.com
#         export CLAUDE_MEM_CLOUD_SYNC_ENABLED=true
Defensive patterns

Strategy: validation

Validate before calling

function resolveHubUrl(env: NodeJS.ProcessEnv = process.env): string {
  const url = (env.CLAUDE_MEM_CLOUD_SYNC_HUB_URL ?? '').trim().replace(/\/+$/, '');
  if (!url) throw new Error('CLAUDE_MEM_CLOUD_SYNC_HUB_URL must be set when cloud sync is enabled');
  try { new URL(url); } catch { throw new Error('CLAUDE_MEM_CLOUD_SYNC_HUB_URL is not a valid URL: ' + url); }
  return url;
}
// call before constructing SyncClient:

Try / catch

let client: SyncClient;
try {
  client = new SyncClient(apply, { hubUrl: resolveHubUrl(), deviceId, /* ... */ });
} catch (e) {
  if (e instanceof Error && e.message.includes('non-empty hubUrl')) {
    logger.warn('SYNC', 'cloud sync disabled: hub url not configured'); // degrade gracefully
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: `new SyncClient(apply, { hubUrl, ... })` where hubUrl is undefined, empty, or all whitespace. The env var CLAUDE_MEM_CLOUD_SYNC_HUB_URL is the documented source; if unset/blank the constructor throws synchronously before any network setup.

Common situations: Cloud sync enabled (e.g. CLAUDE_MEM_CLOUD_SYNC_ENABLED=true) but CLAUDE_MEM_CLOUD_SYNC_HUB_URL not set in the environment or ~/.claude-mem/settings.json, a typo/duplicate in the env var name, or the variable exported as empty. Common in fresh installs or CI where the env isn't provisioned.

Related errors


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