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
- 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.
- If cloud sync is intentionally disabled, unset the enable flag (e.g. CLAUDE_MEM_CLOUD_SYNC_ENABLED=false) so SyncClient is not constructed.
- Confirm the env var is exported in the process that runs the worker (print process.env.CLAUDE_MEM_CLOUD_SYNC_HUB_URL at startup).
- 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
- Always set CLAUDE_MEM_CLOUD_SYNC_HUB_URL when CLAUDE_MEM_CLOUD_SYNC_ENABLED is true.
- Print the resolved env at worker startup to catch missing exports in CI.
- If sync is optional, guard SyncClient construction behind the enable flag.
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
- server startup configuration is invalid: - ${line}
- Transcript watch config not found: ${resolvedPath}
- Postgres requires CLAUDE_MEM_SERVER_DATABASE_URL
- Postgres requires CLAUDE_MEM_SERVER_DATABASE_URL
- Invalid CLAUDE_MEM_WORKER_PORT in settings.json: missing
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/9c739822809c3287.
Report an issue: GitHub.