{"record":{"id":"5310a8dadf58b10a","repo":"thedotmack/claude-mem","slug":"fileedithandler-requires-filepath","errorCode":null,"errorMessage":"fileEditHandler requires filePath","messagePattern":"fileEditHandler requires filePath","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/cli/handlers/file-edit.ts","lineNumber":15,"sourceCode":"\nimport type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';\nimport { executeWithWorkerFallback, isWorkerFallback } from '../../shared/worker-utils.js';\nimport { logger } from '../../utils/logger.js';\nimport { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';\nimport { normalizePlatformSource } from '../../shared/platform-source.js';\nimport { shouldTrackProject } from '../../shared/should-track-project.js';\n\nexport const fileEditHandler: EventHandler = {\n  async execute(input: NormalizedHookInput): Promise<HookResult> {\n    const { sessionId, cwd, filePath, edits } = input;\n    const platformSource = normalizePlatformSource(input.platform);\n\n    if (!filePath) {\n      throw new Error('fileEditHandler requires filePath');\n    }\n\n    logger.dataIn('HOOK', `FileEdit: ${filePath}`, {\n      editCount: edits?.length ?? 0\n    });\n\n    if (!cwd) {\n      throw new Error(`Missing cwd in FileEdit hook input for session ${sessionId}, file ${filePath}`);\n    }\n\n    if (!shouldTrackProject(cwd)) {\n      logger.debug('HOOK', 'Project excluded from tracking, skipping file edit observation', { cwd, filePath });\n      return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };\n    }\n\n    const result = await executeWithWorkerFallback<{ status?: string }>(\n      '/api/sessions/observations',\n      'POST',","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/cli/handlers/file-edit.ts#L1-L33","documentation":"This is a console.warn emitted by claude-mem's own catch block around localStorage.getItem (src/ui/viewer/components/WelcomeCard.tsx:11-18). The Web Storage API can throw at access time: SecurityError when the browser denies storage for the origin (site data/cookies blocked), ReferenceError when the localStorage global does not exist (SSR, Node, jsdom without a storage shim), and opaque-origin errors on sandboxed iframes or about:blank-style contexts. The wrapper is doing its job: it logs the reason and falls back to false so the welcome card still renders.","triggerScenarios":"Specifically triggered when getStoredWelcomeDismissed() runs (it is the lazy initializer at src/ui/viewer/App.tsx:18, so it executes during the very first render) and any of these holds: Chrome set to 'Block all cookies' or the site's data blocked via the padlock icon; the viewer is embedded in a sandboxed iframe without allow-same-origin; the page is served from an opaque or file:// origin; the component renders outside a real browser (SSR/prerender/test runner) so localStorage is undefined; an enterprise policy or extension disables Web Storage.","commonSituations":"Lockdown privacy settings (Chrome 'Block third-party cookies' + site-data blocking, Firefox 'Never Save History' mode which disables storage), Safari content blockers, corporate-managed browsers with storage disabled, running the viewer UI inside a restricted webview/iframe (e.g. an IDE extension webview), and vitest/jsdom tests that execute App.tsx without configuring a localStorage. Also seen after browsers rolled out storage partitioning for embedded contexts.","solutions":["Confirm it is environmental: open DevTools > Application > Local Storage/Session Storage for the page; if the pane errors or the origin is listed as blocked, allow site data for that origin in browser settings and reload.","If the viewer can ever be server-rendered or prerendered, move the read out of the useState initializer into a useEffect, or gate it with typeof window !== 'undefined', because App.tsx:18 executes the read during the first render where localStorage may not exist.","Add a one-time storage-availability probe (try window.localStorage.setItem('__t','1') then removeItem) cached in a module variable, and have getStoredWelcomeDismissed consult it before touching localStorage.","If none apply and the warning appears in an embedded webview/iframe, ask the host to add allow-same-origin (and storage permission) to the sandbox attribute.","Accept the fallback: the function already returns false and the UI degrades to showing the welcome card; if that is acceptable, downgrading this log to console.debug in embedded contexts removes the noise."],"exampleFix":"// before (src/ui/viewer/App.tsx:18) - runs during first render, breaks under SSR/blocked storage\nconst [welcomeDismissed, setWelcomeDismissed] = useState<boolean>(getStoredWelcomeDismissed);\n\n// after - read storage only in the browser, after mount\nconst [welcomeDismissed, setWelcomeDismissed] = useState<boolean>(false);\nuseEffect(() => {\n  setWelcomeDismissed(getStoredWelcomeDismissed());\n}, []);","handlingStrategy":"try-catch","validationCode":"// Probe storage availability once, before the first render reads it\nlet storageOk: boolean | null = null;\nfunction storageAvailable(): boolean {\n  if (storageOk !== null) return storageOk;\n  try {\n    const k = '__claude-mem-probe__';\n    window.localStorage.setItem(k, '1');\n    window.localStorage.removeItem(k);\n    storageOk = true;\n  } catch {\n    storageOk = false;\n  }\n  return storageOk;\n}\n\n// in src/ui/viewer/App.tsx - avoid the read entirely when there is no window/storage\nconst [welcomeDismissed, setWelcomeDismissed] = useState<boolean>(\n  () => typeof window !== 'undefined' && storageAvailable() && getStoredWelcomeDismissed()\n);","typeGuard":"function hasLocalStorage(): boolean {\n  try {\n    return typeof window !== 'undefined' && window.localStorage !== null;\n  } catch {\n    return false; // accessing .localStorage itself can throw SecurityError\n  }\n}","tryCatchPattern":"// Keep the existing shape: catch unknown, narrow to Error for the message,\n// log at warn/debug exactly once, and return a safe default. Never rethrow from a read path.\ntry {\n  return localStorage.getItem(STORAGE_KEY) === 'true';\n} catch (e: unknown) {\n  console.warn('Failed to read welcome-dismissed from localStorage:', e instanceof Error ? e.message : String(e));\n  return false;\n}","preventionTips":["Never touch localStorage during module evaluation or a useState initializer that can run under SSR; defer reads to useEffect or guard with typeof window !== 'undefined'.","Wrap every storage access (even reads - .getItem and property access can throw SecurityError) in try/catch with a typed fallback.","Cache a storage-availability probe at startup and skip all storage calls when it fails, so you log one warning instead of one per access.","Test the viewer with storage blocked (Chrome: padlock > Site settings > Block cookies) and in jsdom without a storage shim to verify graceful degradation.","Report the error's name (e.name) alongside the message so SecurityError vs QuotaExceededError vs ReferenceError is distinguishable in logs."],"tags":["localstorage","web-storage-api","browser-storage","client-side","privacy-settings","ssr"],"backgroundTag":"localstorage-unavailable","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}