danielmiessler/Fabric · warning · Error

Invalid session file format

Error message

Invalid session file format

What it means

Thrown by importFromFile in session-store.ts after a user-selected .json file was parsed successfully but the parsed value is not an array. The store's Message[] contract means any valid session export must be a JSON array of message objects; a JSON object, string, number, or null fails the Array.isArray check. This is a user-data validation error, not a parse error (readFileAsJson already succeeded).

Source

Thrown at web/src/lib/store/session-store.ts:83

    try {
      await saveToFile(messages, 'session-history.json');
      toastService.success('Session exported successfully');
    } catch (error) {
      toastService.error('Failed to export session');
      throw error;
    }
  },

  async importFromFile(): Promise<Message[]> {
    try {
      const file = await openFileDialog('.json');
      if (!file) {
        throw new Error('No file selected');
      }

      const content = await readFileAsJson<Message[]>(file);
      if (!Array.isArray(content)) {
        throw new Error('Invalid session file format');
      }

      toastService.success('Session imported successfully');
      return content;
    } catch (error) {
      toastService.error(error instanceof Error ? error.message : 'Failed to import session');
      throw error;
    }
  },

  async loadSessionMessages(sessionName: string): Promise<Message[]> {
    try {
      const response = await fetch(`/api/sessions/${sessionName}`);
      if (!response.ok) {
        throw new Error(`Failed to load session: ${response.statusText}`);
      }
      const data = await response.json();
      const messages = Array.isArray(data.Message) ? data.Message : [];

View on GitHub (pinned to 338b89cfe9)

Solutions

  1. Open the imported file and confirm the top-level value is a JSON array; if it is wrapped (e.g. {messages: [...]} or {Message: [...]}), unwrap it before importing
  2. Re-export a session from the app itself to get a known-good array-shaped file
  3. If exports legitimately vary, accept both shapes: use Array.isArray(content) ? content : Array.isArray(content?.messages ?? content?.Message) ? (content.messages ?? content.Message) : null and only throw when both fail
  4. Add per-item validation so a non-Message array element fails early with a clear message instead of breaking render later

Example fix

// before
const content = await readFileAsJson<Message[]>(file);
if (!Array.isArray(content)) {
  throw new Error('Invalid session file format');
}

// after
const content = await readFileAsJson<unknown>(file);
const messages = Array.isArray(content)
  ? content
  : Array.isArray((content as any)?.messages)
    ? (content as any).messages
    : Array.isArray((content as any)?.Message)
      ? (content as any).Message
      : null;
if (!messages || !messages.every(m => m && typeof m.role === 'string')) {
  throw new Error('Invalid session file format: expected an array of messages');
}
Defensive patterns

Strategy: validation

Validate before calling

// Run before accepting the file content
import { readFileAsJson } from './file-utils';

async function parseSessionFile(file: File): Promise<Message[]> {
  const content = await readFileAsJson<unknown>(file);
  const candidate = Array.isArray(content)
    ? content
    : Array.isArray((content as Record<string, unknown>)?.messages)
      ? (content as { messages: unknown[] }).messages
      : null;
  if (!candidate) throw new Error('Invalid session file format: expected an array of messages');
  return candidate as Message[];
}

Type guard

function isMessageArray(v: unknown): v is Message[] {
  return (
    Array.isArray(v) &&
    v.every(
      (m) =>
        m !== null &&
        typeof m === 'object' &&
        typeof (m as Message).role === 'string' &&
        typeof (m as Message).content === 'string'
    )
  );
}

Try / catch

try {
  const messages = await parseSessionFile(file);
  toastService.success('Session imported successfully');
  return messages;
} catch (error) {
  toastService.error(error instanceof Error ? error.message : 'Failed to import session');
  return []; // do not rethrow into UI event handlers that don't catch
}

Prevention

When it happens

Trigger: Selecting a file that is valid JSON but not an array: a single exported message object ({"role":"user",...}), a wrapped export like {"messages":[...]}, an empty JSON file parsed as null, or a config file accidentally picked in the .json file dialog.

Common situations: Hand-edited exports, exports from a different app version that wrapped messages in an envelope, picking the wrong file (pattern config, package.json), or an export that contains {Message: [...]} from the server API shape rather than the client's bare-array shape.

Related errors


AI-assisted analysis of danielmiessler/Fabric@338b89cfe9 (2026-08-15). Data as JSON: /api/errors/5a31d5c75fb0fa6a. Report an issue: GitHub.