kopia/kopia · error

error writing format blob

Error message

error writing format blob

What it means

During `kopia repository repair`, after a replica of the repository format blob is found via RecoverFormatBlob, Kopia writes it back under the canonical blob ID (format.KopiaRepositoryBlobID) with PutBlob. If that write fails, the error is wrapped as "error writing format blob". This means a replica was found but the storage backend refused the write that would restore it.

Solutions

  1. Inspect the wrapped puterr to identify the backend-specific cause (permissions, quota, transient).
  2. Verify the storage location is writable (IAM write permissions, disk space, not mounted read-only).
  3. Re-run the repair; the listing step will re-find the replica and retry the write.
  4. Use `--dry-run` first to confirm a replica exists before attempting the real write.

Example fix

// before
if puterr := st.PutBlob(ctx, format.KopiaRepositoryBlobID, gather.FromSlice(b), blob.PutOptions{}); puterr != nil {
	return errors.Wrap(puterr, "error writing format blob")
}
// after
if puterr := st.PutBlob(ctx, format.KopiaRepositoryBlobID, gather.FromSlice(b), blob.PutOptions{}); puterr != nil {
	return errors.Wrap(puterr, "error writing format blob") // ensure storage is writable & has free quota
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: probe write access before repair
probeID := blob.ID("kopia-repair-write-probe")
if err := st.PutBlob(ctx, probeID, gather.FromSlice([]byte{1}), blob.PutOptions{}); err != nil {
	return fmt.Errorf("storage not writable, aborting repair: %w", err)
}
_ = st.DeleteBlob(ctx, probeID)

Try / catch

if puterr := st.PutBlob(ctx, format.KopiaRepositoryBlobID, gather.FromSlice(b), blob.PutOptions{}); puterr != nil {
	return errors.Wrap(puterr, "error writing format blob") // check IAM write perms, quota, read-only mount
}

Prevention

When it happens

Trigger: PutBlob to format.KopiaRepositoryBlobID fails — storage backend I/O error, permission denied on the bucket/container, read-only storage mount, quota exceeded, or provider transient failure — while repairing without --dry-run.

Common situations: Repairing a repository whose storage credentials allow read but not write; a full S3 bucket or disk; a read-only mounted filesystem path; transient cloud-provider 5xx during repair.

Related errors


AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07). Data as JSON: /api/errors/13659eec5d18d55b. Report an issue: GitHub.

Appendix: source

Thrown at cli/command_repository_repair.go:92

	prefixes := c.repairCommandRecoverFormatBlobPrefixes
	if len(prefixes) == 0 {
		prefixes = packBlockPrefixes()
	}

	return c.recoverFormatBlob(ctx, st, prefixes)
}

func (c *commandRepositoryRepair) recoverFormatBlob(ctx context.Context, st blob.Storage, prefixes []string) error {
	errSuccess := errors.New("success")

	for _, prefix := range prefixes {
		err := st.ListBlobs(ctx, blob.ID(prefix), func(bi blob.Metadata) error {
			log(ctx).Infof("looking for replica of format blob in %v...", bi.BlobID)

			if b, err := format.RecoverFormatBlob(ctx, st, bi.BlobID, bi.Length); err == nil {
				if !c.repairDryRun {
					if puterr := st.PutBlob(ctx, format.KopiaRepositoryBlobID, gather.FromSlice(b), blob.PutOptions{}); puterr != nil {
						return errors.Wrap(puterr, "error writing format blob")
					}
				}

				log(ctx).Infof("recovered replica block from %v", bi.BlobID)

				return errSuccess
			}

			return nil
		})

		switch {
		case err == nil:
			// do nothing
		case errors.Is(err, errSuccess):
			return nil
		default:
			return errors.Wrap(err, "unexpected error when listing blobs")

View on GitHub (pinned to 82495e54b5)