{"record":{"id":"2a0f479729a75919","repo":"thedotmack/claude-mem","slug":"failed-to-read-existing-settings-file-starting-fr","errorCode":null,"errorMessage":"Failed to read existing settings file; starting fresh","messagePattern":"Failed to read existing settings file; starting fresh","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/hooks/server-bootstrap.ts","lineNumber":144,"sourceCode":"  }\n}\n\nexport function persistServerSettings(\n  settingsPath: string,\n  values: { apiKey: string; projectId: string; serverBaseUrl?: string },\n): void {\n  const dir = dirname(settingsPath);\n  if (!existsSync(dir)) {\n    mkdirSync(dir, { recursive: true });\n  }\n\n  let existing: Record<string, unknown> = {};\n  if (existsSync(settingsPath)) {\n    try {\n      existing = readJsonFileWithBom<Record<string, unknown>>(settingsPath);\n    } catch (error) {\n      const err = error instanceof Error ? error : new Error(String(error));\n      logger.warn('HOOK', 'Failed to read existing settings file; starting fresh', { settingsPath }, err);\n      existing = {};\n    }\n  }\n  // Settings file format: support both the flat shape (modern) and the\n  // env-nested shape (Claude-Code-style: { env: {...}, hooks: [...], ... }).\n  // `flat` is a *reference* into `existing` — the env subtree when nested, or\n  // the root document otherwise — so mutating `flat` mutates `existing` in\n  // place. We then write the full `existing` document below (NOT `flat`), so\n  // non-env top-level keys (hooks, permissions, apiKeyHelper, ...) survive.\n  // Writing `flat` back as the whole file silently dropped them (data loss).\n  const flat = (existing.env && typeof existing.env === 'object'\n    ? existing.env\n    : existing) as Record<string, unknown>;\n\n  // Phase 1d: write the new canonical settings keys. Legacy\n  // `CLAUDE_MEM_SERVER_BETA_*` keys are dual-accepted by reads in\n  // `runtime-selector.ts`, so existing installs continue to work. Any\n  // legacy keys that already live in `flat` are left untouched (we don't","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/hooks/server-bootstrap.ts#L126-L162","documentation":"During hook bootstrap, claude-mem merges its settings into an existing settings JSON file. If the file exists but readJsonFileWithBom throws (corrupted JSON, unreadable file), this warning is logged and `existing` resets to {}. The merge then proceeds against an empty document, so the subsequent write persists only claude-mem's freshly generated keys — previously stored env, hooks, permissions, and apiKeyHelper entries are dropped.","triggerScenarios":"settingsPath exists and existsSync passes, but readJsonFileWithBom throws: SyntaxError from malformed or truncated JSON, EACCES on a permission-restricted file, or an encoding anomaly the reader cannot handle.","commonSituations":"Settings file truncated by a crash or power loss mid-write; hand-edited settings with a trailing comma or a comment; two tools writing the file concurrently; restrictive permissions after a user or home-directory migration.","solutions":["Back up the settings file immediately — after this warning the next write replaces it with only claude-mem keys.","Run a JSON validator on the file and fix the syntax error the parse reports (the logged Error carries the position).","Check permissions/ownership on settingsPath if the error is EACCES rather than SyntaxError.","Restore lost hooks/permissions/env keys from a backup or dotfiles repo, then re-run the bootstrap."],"exampleFix":"// settings.json — before (trailing comma: unparseable, prior keys get wiped)\n{ \"env\": { \"CLAUDE_MEM\": \"1\" }, }\n// after (valid JSON; existing keys survive the merge)\n{ \"env\": { \"CLAUDE_MEM\": \"1\" } }","handlingStrategy":"validation","validationCode":"// pre-flight the settings file before any tool merges into it\nimport { readFileSync } from 'node:fs';\n\nif (existsSync(settingsPath)) {\n  const txt = readFileSync(settingsPath, 'utf-8');\n  try {\n    JSON.parse(txt);\n  } catch (e) {\n    copyFileSync(settingsPath, `${settingsPath}.corrupt-${Date.now()}`); // preserve\n    throw new Error(`settings file is not valid JSON: ${settingsPath}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  bootstrap(settingsPath);\n} catch (e) {\n  // a warn here means prior keys were reset: restore hooks/permissions from backup\n  restoreFromBackup(settingsPath);\n}","preventionTips":["Keep settings.json in a dotfiles repo or backup before upgrades.","Never add comments or trailing commas to the settings JSON.","Ensure only one tool writes the settings file at a time."],"tags":["settings","json","data-loss","bootstrap"],"backgroundTag":"json-parse-error","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}