chatboxai/chatbox · error · Error

Backup JSON entry is too large

Error message

Backup JSON entry is too large: ${path}

What it means

Thrown by parseJson during the import reading phase when an entry's decoded UTF-8 bytes exceed MAX_BACKUP_JSON_ENTRY_BYTES (128 MiB). It guards JSON.parse against pathological inputs. readZipFileEntries already caps each entry via backupEntryByteLimit, so this is a secondary defense; if reached, the import aborts, rollback runs (commitStarted is false, so only temps are cleaned), and the error propagates.

Solutions

  1. Reduce the offending JSON entry below 128 MiB (split the session, trim message history) and re-export.
  2. Reject the file before import by pre-scanning entry.uncompressedSize.
  3. Raise MAX_BACKUP_JSON_ENTRY_BYTES only after verifying the memory budget for JSON.parse.
  4. Regenerate the backup from a current app version.

Example fix

// before: import blows up parsing a 200 MiB session.json
await importBackupArchive(file, options)
// after: pre-scan and reject oversized entries
await readZipFileEntries(file, async (entry) => {
  if (isBackupJsonPath(entry.path) && entry.uncompressedSize > MAX_BACKUP_JSON_ENTRY_BYTES) {
    throw new Error('Refusing oversized entry ' + entry.path)
  }
})
await importBackupArchive(file, options)
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-scan the zip's JSON entries and reject any over the JSON cap before importing.
import { MAX_BACKUP_JSON_ENTRY_BYTES } from './types'
import { isBackupJsonPath } from './archive-layout'
await readZipFileEntries(file, async (entry) => {
  if (isBackupJsonPath(entry.path) && entry.uncompressedSize > MAX_BACKUP_JSON_ENTRY_BYTES) {
    throw new Error('Entry too large to import: ' + entry.path)
  }
})

Type guard

// True when a JSON entry's uncompressed size fits the JSON budget.
const jsonEntryFits = (uncompressedSize: number): boolean =>
  uncompressedSize <= MAX_BACKUP_JSON_ENTRY_BYTES

Try / catch

try {
  await importBackupArchive(file, options)
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Backup JSON entry is too large')) {
    // The offending path is embedded in the message; trim the source and re-export.
    throw new Error('Cannot import: a JSON entry exceeds 128 MiB', { cause: error })
  }
  throw error
}

Prevention

When it happens

Trigger: A JSON archive entry (manifest.json, settings.json, copilots.json, session-settings.json, or a sessions/<id>/session.json) is larger than 128 MiB uncompressed and reached parseJson.

Common situations: A session with an enormous message history; a hand-edited or future-format archive with a giant JSON entry; entry-limit config bypassed when calling readZipFileEntries.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/822d9ecd33f701ff. Report an issue: GitHub.

Appendix: source

Thrown at src/renderer/packages/backup/import-backup.ts:76

    session: Session
  ) => Promise<{ session: Session; warnings: BackupWarning[]; rollback?: () => Promise<void> }>
}

export interface BackupImportResult {
  manifest: BackupManifest
  warnings: BackupWarning[]
  restoredSessionCount: number
  restoredResourceCount: number
}

function throwIfAborted(signal?: AbortSignal) {
  if (signal?.aborted) {
    throw signal.reason instanceof Error ? signal.reason : new DOMException('Operation canceled', 'AbortError')
  }
}

function parseJson(bytes: Uint8Array, path: string): unknown {
  if (bytes.length > MAX_BACKUP_JSON_ENTRY_BYTES) throw new Error(`Backup JSON entry is too large: ${path}`)
  return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)) as unknown
}

function validateManifestEntries(manifest: BackupManifest, stagedEntries: Map<string, StagedEntry>) {
  const descriptors = [
    manifest.data.settings,
    manifest.data.copilots,
    manifest.data.sessionSettings,
    ...manifest.sessions,
    ...manifest.resources,
  ].filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))
  const expectedPaths = new Set<string>([BACKUP_MANIFEST_PATH])
  for (const descriptor of descriptors) {
    if (expectedPaths.has(descriptor.path)) throw new Error(`Manifest contains a duplicate path: ${descriptor.path}`)
    expectedPaths.add(descriptor.path)
    const staged = stagedEntries.get(descriptor.path)
    if (!staged) throw new Error(`Backup entry is missing: ${descriptor.path}`)
    if (staged.size !== descriptor.size) throw new Error(`Backup entry size mismatch: ${descriptor.path}`)

View on GitHub (pinned to 81571269ad)