thedotmack/claude-mem · warning

Missing cwd in FileEdit hook input for session ${sessionId},

Error message

Missing cwd in FileEdit hook input for session ${sessionId}, file ${filePath}

What it means

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.

Source

Thrown at src/cli/handlers/file-edit.ts:23

import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
import { normalizePlatformSource } from '../../shared/platform-source.js';
import { shouldTrackProject } from '../../shared/should-track-project.js';

export const fileEditHandler: EventHandler = {
  async execute(input: NormalizedHookInput): Promise<HookResult> {
    const { sessionId, cwd, filePath, edits } = input;
    const platformSource = normalizePlatformSource(input.platform);

    if (!filePath) {
      throw new Error('fileEditHandler requires filePath');
    }

    logger.dataIn('HOOK', `FileEdit: ${filePath}`, {
      editCount: edits?.length ?? 0
    });

    if (!cwd) {
      throw new Error(`Missing cwd in FileEdit hook input for session ${sessionId}, file ${filePath}`);
    }

    if (!shouldTrackProject(cwd)) {
      logger.debug('HOOK', 'Project excluded from tracking, skipping file edit observation', { cwd, filePath });
      return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
    }

    const result = await executeWithWorkerFallback<{ status?: string }>(
      '/api/sessions/observations',
      'POST',
      {
        contentSessionId: sessionId,
        platformSource,
        tool_name: 'write_file',
        tool_input: { filePath, edits },
        tool_response: { success: true },
        cwd,
      },

View on GitHub (pinned to e2d1df569a)

Solutions

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. For embedded/sandboxed contexts, request allow-same-origin plus storage access from the embedding host, or accept the re-show behavior.

Example fix

// before - fire-and-forget write, quota/security failures only logged
localStorage.setItem(STORAGE_KEY, 'true');

// after - classify the failure, evict and retry once on quota, keep an in-memory fallback
try {
  localStorage.setItem(STORAGE_KEY, 'true');
} catch (e) {
  if (e instanceof DOMException && e.name === 'QuotaExceededError') {
    evictStaleKeys(); // remove old non-essential keys for this origin
    try { localStorage.setItem(STORAGE_KEY, 'true'); return; } catch { /* fall through */ }
  }
  memoryFallback[STORAGE_KEY] = 'true'; // dismissal still works for this session
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before writing, confirm writable storage and remaining headroom
function canWriteStorage(): boolean {
  try {
    const k = '__claude-mem-probe__';
    window.localStorage.setItem(k, '1');
    window.localStorage.removeItem(k);
    return true;
  } catch {
    return false;
  }
}

if (canWriteStorage()) {
  setStoredWelcomeDismissed(true);
} else {
  sessionStorage.setItem(STORAGE_KEY, 'true'); // session-scoped fallback
}

Type guard

function isQuotaExceeded(e: unknown): e is DOMException & { name: 'QuotaExceededError' } {
  return e instanceof DOMException &&
    (e.name === 'QuotaExceededError' || e.code === 22);
}

function isStorageDenied(e: unknown): e is DOMException & { name: 'SecurityError' } {
  return e instanceof DOMException && e.name === 'SecurityError';
}

Try / catch

// Write-specific pattern: classify, retry once on quota, otherwise degrade silently
try {
  localStorage.setItem(STORAGE_KEY, 'true');
} catch (e: unknown) {
  if (isQuotaExceeded(e)) {
    evictStaleKeysForOrigin();
    try { localStorage.setItem(STORAGE_KEY, 'true'); } catch { /* give up quietly */ }
  } else {
    console.warn('welcome-dismissed not persisted (storage blocked):', e instanceof Error ? e.message : String(e));
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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