{"record":{"id":"5a31d5c75fb0fa6a","repo":"danielmiessler/Fabric","slug":"invalid-session-file-format","errorCode":null,"errorMessage":"Invalid session file format","messagePattern":"Invalid session file format","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"web/src/lib/store/session-store.ts","lineNumber":83,"sourceCode":"    try {\n      await saveToFile(messages, 'session-history.json');\n      toastService.success('Session exported successfully');\n    } catch (error) {\n      toastService.error('Failed to export session');\n      throw error;\n    }\n  },\n\n  async importFromFile(): Promise<Message[]> {\n    try {\n      const file = await openFileDialog('.json');\n      if (!file) {\n        throw new Error('No file selected');\n      }\n\n      const content = await readFileAsJson<Message[]>(file);\n      if (!Array.isArray(content)) {\n        throw new Error('Invalid session file format');\n      }\n\n      toastService.success('Session imported successfully');\n      return content;\n    } catch (error) {\n      toastService.error(error instanceof Error ? error.message : 'Failed to import session');\n      throw error;\n    }\n  },\n\n  async loadSessionMessages(sessionName: string): Promise<Message[]> {\n    try {\n      const response = await fetch(`/api/sessions/${sessionName}`);\n      if (!response.ok) {\n        throw new Error(`Failed to load session: ${response.statusText}`);\n      }\n      const data = await response.json();\n      const messages = Array.isArray(data.Message) ? data.Message : [];","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/danielmiessler/Fabric/blob/338b89cfe97ab2d12ce30ce8b5449857a841366d/web/src/lib/store/session-store.ts#L65-L101","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Re-export a session from the app itself to get a known-good array-shaped file","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","Add per-item validation so a non-Message array element fails early with a clear message instead of breaking render later"],"exampleFix":"// before\nconst content = await readFileAsJson<Message[]>(file);\nif (!Array.isArray(content)) {\n  throw new Error('Invalid session file format');\n}\n\n// after\nconst content = await readFileAsJson<unknown>(file);\nconst messages = Array.isArray(content)\n  ? content\n  : Array.isArray((content as any)?.messages)\n    ? (content as any).messages\n    : Array.isArray((content as any)?.Message)\n      ? (content as any).Message\n      : null;\nif (!messages || !messages.every(m => m && typeof m.role === 'string')) {\n  throw new Error('Invalid session file format: expected an array of messages');\n}","handlingStrategy":"validation","validationCode":"// Run before accepting the file content\nimport { readFileAsJson } from './file-utils';\n\nasync function parseSessionFile(file: File): Promise<Message[]> {\n  const content = await readFileAsJson<unknown>(file);\n  const candidate = Array.isArray(content)\n    ? content\n    : Array.isArray((content as Record<string, unknown>)?.messages)\n      ? (content as { messages: unknown[] }).messages\n      : null;\n  if (!candidate) throw new Error('Invalid session file format: expected an array of messages');\n  return candidate as Message[];\n}","typeGuard":"function isMessageArray(v: unknown): v is Message[] {\n  return (\n    Array.isArray(v) &&\n    v.every(\n      (m) =>\n        m !== null &&\n        typeof m === 'object' &&\n        typeof (m as Message).role === 'string' &&\n        typeof (m as Message).content === 'string'\n    )\n  );\n}","tryCatchPattern":"try {\n  const messages = await parseSessionFile(file);\n  toastService.success('Session imported successfully');\n  return messages;\n} catch (error) {\n  toastService.error(error instanceof Error ? error.message : 'Failed to import session');\n  return []; // do not rethrow into UI event handlers that don't catch\n}","preventionTips":["Always export sessions from the app itself so the file shape matches the importer","Validate shape (isMessageArray) before assigning to stores, not after render","Keep the export format documented and versioned so future shape changes are intentional"],"tags":["validation","json","file-import","session-store"],"backgroundTag":null,"analyzedSha":"338b89cfe97ab2d12ce30ce8b5449857a841366d","analyzedAt":"2026-08-15T11:38:51.759Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}