chatboxai/chatbox · error · Error

Backup archive was not fully generated

Error message

Backup archive was not fully generated

What it means

Thrown by exportBackupArchive after writeArchive returns if completedManifest is still undefined. completedManifest is only assigned at the end of the generateEntries async generator (line 398, after the manifest entry is yielded at line 401), so if the generator never fully ran the manifest is missing. Unlike the in-loop throws this one propagates to the caller and aborts the export.

Source

Thrown at src/renderer/packages/backup/export-backup.ts:408

      stats: {
        sessionCount: sessions.length,
        resourceCount: resources.length,
        deduplicatedResourceCount: Math.max(0, successfullyReadResourceKeys - resources.length),
        warningCount: warnings.length,
      },
    })
    validateBackupManifestGraph(manifest)
    completedManifest = manifest
    options.onProgress?.({ phase: 'packing', current: 0, total: 1 })
    const manifestEntry = await jsonEntry(BACKUP_MANIFEST_PATH, completedManifest)
    yield manifestEntry.archive
    options.onProgress?.({ phase: 'packing', current: 1, total: 1 })
  }

  const writeResult = await options.writeArchive(() =>
    createZipStream(enforceArchiveLimits(generateEntries()), options.signal)
  )
  if (!completedManifest) throw new Error('Backup archive was not fully generated')
  return {
    manifest: completedManifest,
    boundedMemory: writeResult.boundedMemory,
    pendingDownload: writeResult.pendingDownload,
  }
}

View on GitHub (pinned to 81571269ad)

Solutions

  1. Make writeArchive fully drain the async generator it receives before resolving its promise.
  2. Do not catch errors inside writeArchive without rethrowing; let them propagate so exportBackupArchive fails loudly.
  3. If writeArchive must bound memory, stream every chunk to the sink instead of stopping early.
  4. Add a test asserting writeArchive consumes the generator to completion.

Example fix

// before: stops after the first chunk, manifest never emitted
const writeArchive = async (dataCallback) => {
  for await (const chunk of dataCallback()) { sink.write(chunk); break }
  return { boundedMemory: 0, pendingDownload: Promise.resolve() }
}
// after: drain the whole generator before resolving
const writeArchive = async (dataCallback) => {
  let boundedMemory = 0
  for await (const chunk of dataCallback()) { sink.write(chunk); boundedMemory++ }
  return { boundedMemory, pendingDownload: Promise.resolve() }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Assert your writeArchive drains the generator to completion before wiring it in.
const assertDrains = async (writeArchive: BackupExportOptions['writeArchive']) => {
  let count = 0
  await writeArchive(async function* () { for (let i = 0; i < 5; i++) { yield new Uint8Array(); count++ } })
  if (count !== 5) throw new Error('writeArchive does not drain the generator')
}

Try / catch

try {
  await exportBackupArchive(options)
} catch (error) {
  if (error instanceof Error && error.message === 'Backup archive was not fully generated') {
    // writeArchive did not consume the generator to the manifest entry.
    throw new Error('Export failed: archive writer stopped early', { cause: error })
  }
  throw error
}

Prevention

When it happens

Trigger: options.writeArchive never invoked its dataCallback; the dataCallback did not fully iterate the returned async generator (stopped after a bounded number of chunks); or it swallowed an error thrown inside the generator instead of propagating it, leaving completedManifest unset.

Common situations: A custom writeArchive that breaks out of the for-await loop early (e.g. streamed only the first chunk to disk); a writeArchive that catches and discards generator errors; a generator that stopped before yielding the manifest because of an earlier swallowed throw.

Related errors


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