{"record":{"id":"12f5d41337280215","repo":"thedotmack/claude-mem","slug":"missing-cwd-in-fileedit-hook-input-for-session-s","errorCode":null,"errorMessage":"Missing cwd in FileEdit hook input for session ${sessionId}, file ${filePath}","messagePattern":"Missing cwd in FileEdit hook input for session (.+?), file (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/cli/handlers/file-edit.ts","lineNumber":23,"sourceCode":"import { 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',\n      {\n        contentSessionId: sessionId,\n        platformSource,\n        tool_name: 'write_file',\n        tool_input: { filePath, edits },\n        tool_response: { success: true },\n        cwd,\n      },","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/cli/handlers/file-edit.ts#L5-L41","documentation":"This is a console.warn from claude-mem's catch block around localStorage.setItem/removeItem (src/ui/viewer/components/WelcomeCard.tsx:20-30). Web Storage writes throw in two main cases: QuotaExceededError when the origin's ~5MB localStorage budget is exhausted (or is 0, as in old Safari private browsing where setItem always threw), and SecurityError when the browser has blocked site data for the origin. Because only the string 'true' is written, quota exhaustion is almost never this key's fault - the origin is already full, or writes are categorically blocked.","triggerScenarios":"setStoredWelcomeDismissed(true) is called when the user dismisses the welcome modal (WelcomeCard.tsx:164), and setStoredWelcomeDismissed(false) on reset (App.tsx:106). The write throws when: the origin's localStorage is already at the 5MB cap (large saved sessions/observations under other keys); the user is in old Safari Private Browsing (historically quota 0); site data is blocked (SecurityError); or the context is a partitioned/sandboxed iframe where writes are denied.","commonSituations":"A long-lived viewer origin bloated with cached data that finally crosses the quota; users in private/incognito sessions on older Safari; Chrome's 'Block all cookies' or per-site data blocking; the viewer embedded in a third-party iframe after storage partitioning; jsdom tests writing without a storage implementation. Symptom: dismissal is forgotten and the welcome card reappears every session.","solutions":["Check DevTools > Application > Local Storage: if the origin shows ~5MB used, clear stale keys (or export and purge old claude-mem caches) so this one-key write can succeed again.","Verify site data is allowed for the origin (padlock icon > Site settings > Cookies and site data); blocked storage produces SecurityError on every write regardless of quota.","Add a quota-aware write: catch the error, inspect e.name === 'QuotaExceededError', evict the largest/oldest non-essential keys under your control, then retry setItem once.","If persistence is best-effort (it is - a boolean preference), mirror the flag to sessionStorage or an in-memory fallback so dismissal survives the session even when localStorage is unwritable.","For embedded/sandboxed contexts, request allow-same-origin plus storage access from the embedding host, or accept the re-show behavior."],"exampleFix":"// before - fire-and-forget write, quota/security failures only logged\nlocalStorage.setItem(STORAGE_KEY, 'true');\n\n// after - classify the failure, evict and retry once on quota, keep an in-memory fallback\ntry {\n  localStorage.setItem(STORAGE_KEY, 'true');\n} catch (e) {\n  if (e instanceof DOMException && e.name === 'QuotaExceededError') {\n    evictStaleKeys(); // remove old non-essential keys for this origin\n    try { localStorage.setItem(STORAGE_KEY, 'true'); return; } catch { /* fall through */ }\n  }\n  memoryFallback[STORAGE_KEY] = 'true'; // dismissal still works for this session\n}","handlingStrategy":"try-catch","validationCode":"// Before writing, confirm writable storage and remaining headroom\nfunction canWriteStorage(): boolean {\n  try {\n    const k = '__claude-mem-probe__';\n    window.localStorage.setItem(k, '1');\n    window.localStorage.removeItem(k);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nif (canWriteStorage()) {\n  setStoredWelcomeDismissed(true);\n} else {\n  sessionStorage.setItem(STORAGE_KEY, 'true'); // session-scoped fallback\n}","typeGuard":"function isQuotaExceeded(e: unknown): e is DOMException & { name: 'QuotaExceededError' } {\n  return e instanceof DOMException &&\n    (e.name === 'QuotaExceededError' || e.code === 22);\n}\n\nfunction isStorageDenied(e: unknown): e is DOMException & { name: 'SecurityError' } {\n  return e instanceof DOMException && e.name === 'SecurityError';\n}","tryCatchPattern":"// Write-specific pattern: classify, retry once on quota, otherwise degrade silently\ntry {\n  localStorage.setItem(STORAGE_KEY, 'true');\n} catch (e: unknown) {\n  if (isQuotaExceeded(e)) {\n    evictStaleKeysForOrigin();\n    try { localStorage.setItem(STORAGE_KEY, 'true'); } catch { /* give up quietly */ }\n  } else {\n    console.warn('welcome-dismissed not persisted (storage blocked):', e instanceof Error ? e.message : String(e));\n  }\n}","preventionTips":["Treat storage writes as best-effort: keep the payload tiny (this key stores just 'true') and never let a failed write break the dismiss flow in the UI.","Prune or version your storage keys (the v3 suffix on STORAGE_KEY already does this) and evict data from obsolete versions so quota creep does not doom future writes.","On quota failure, retry once after evicting your own stale keys; do not loop retries against a hard-blocked origin.","Keep a sessionStorage or in-memory mirror for UI preferences so a blocked localStorage only costs persistence, not correctness.","Include e.name (QuotaExceededError/SecurityError) in logs and cap the warning to once per session to avoid console spam."],"tags":["localstorage","quota-exceeded","web-storage-api","browser-storage","private-browsing","client-side"],"backgroundTag":"localstorage-quota-exceeded","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}