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
- Obtain a writable backup handle (StartBackup) instead of a read-only one before calling AddFile.
- Check bh.readOnly (or the handle type/mode) before calling AddFile and branch accordingly.
- 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
- Keep write flows (backup) and read flows (restore/list) on separate, clearly named handle variables.
- Never reuse a handle from ListBackups for writing.
- Type or wrap handles so writable vs read-only is visible at the call site.
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
- EndBackup cannot be called on read-only backup
- AbortBackup cannot be called on read-only backup
- ReadFile cannot be called on read-write backup
- ErrPartSize
- --s3-backup-storage-bucket required
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/c918fc332c890df5.
Report an issue: GitHub.