vitessio/vitess · error

AddFile cannot be called on read-only backup

Error message

AddFile cannot be called on read-only backup

What it means

S3BackupHandle.AddFile refuses to open a new file for writing when the handle was created read-only (bh.readOnly). Read-only handles are for listing/downloading existing backups; writing would corrupt the invariant that read-only handles never mutate backup state. This is an API misuse guard, not a storage failure.

Source

Thrown at go/vt/mysqlctl/s3backupstorage/s3.go:226

	readOnly  bool
	waitGroup sync.WaitGroup
	errorsbackup.PerFileErrorRecorder
}

// Directory is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) Directory() string {
	return bh.dir
}

// Name is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) Name() string {
	return bh.name
}

// AddFile is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) AddFile(ctx context.Context, filename string, filesize int64) (io.WriteCloser, error) {
	if bh.readOnly {
		return nil, errors.New("AddFile cannot be called on read-only backup")
	}

	partSizeBytes, err := calculateUploadPartSize(filesize)
	if err != nil {
		return nil, err
	}

	bh.bs.params.Logger.Infof("Using S3 upload part size: %s", humanize.IBytes(uint64(partSizeBytes)))

	reader, writer := io.Pipe()
	bh.handleAddFile(ctx, filename, partSizeBytes, reader, func(err error) {
		reader.CloseWithError(err)
	})

	return writer, nil
}

func (bh *S3BackupHandle) handleAddFile(ctx context.Context, filename string, partSizeBytes int64, reader io.Reader, closer func(error)) {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Obtain a writable backup handle (StartBackup) instead of a read-only one before calling AddFile.
  2. Check bh.readOnly (or the handle type/mode) before calling AddFile and branch accordingly.
  3. If appending to an existing backup is the goal, that is unsupported — create a new backup instead.

Example fix

// before
bh, _ := backupstorage.GetBackupHandle()
roHandle, _ := bmu.ListBackups(ctx) // read-only handle
roHandle.AddFile(ctx, "file", size) // error

// after
writableHandle, err := bmu.StartBackup(ctx, backupName)
if err != nil {
    return err
}
wc, err := writableHandle.AddFile(ctx, "file", size)
Defensive patterns

Strategy: validation

Validate before calling

if bh.ReadOnly() { // or equivalent mode check on the handle
    return errors.New("refusing to AddFile on read-only backup handle")
}

Try / catch

wc, err := bh.AddFile(ctx, filename, size)
if err != nil {
    if strings.Contains(err.Error(), "read-only backup") {
        return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "wrong handle mode: cannot write via read-only backup handle")
    }
    return err
}

Prevention

When it happens

Trigger: Calling S3BackupHandle.AddFile on a handle obtained via a read-only constructor/path (e.g. listing or restoring a backup), where bh.readOnly is true — immediately returns "AddFile cannot be called on read-only backup".

Common situations: Restore code accidentally using the read-only handle from ListBackups instead of opening a writable handle; copy/paste between backup (write) and restore (read) code paths; attempting to append files to an already-completed backup.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/c918fc332c890df5. Report an issue: GitHub.