vitessio/vitess · error
EndBackup cannot be called on read-only backup
Error message
EndBackup cannot be called on read-only backup
What it means
S3BackupHandle.EndBackup finalizes a multipart upload and must only run on a writable handle. If bh.readOnly is true, the handle was never opened for writing, so calling EndBackup is a misuse and this error is returned immediately (before bh.Wait()/bh.Error()). Read-only handles (used for restore/list) have nothing to finalize.
Source
Thrown at go/vt/mysqlctl/s3backupstorage/s3.go:317
"%w, currently set to %s",
ErrPartSize, humanize.IBytes(uint64(minPartSize)),
)
}
partSizeBytes = int64(minPartSize)
}
return
}
// Wait is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) Wait() {
bh.waitGroup.Wait()
}
// EndBackup is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) EndBackup(ctx context.Context) error {
if bh.readOnly {
return errors.New("EndBackup cannot be called on read-only backup")
}
bh.Wait()
return bh.Error()
}
// AbortBackup is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) AbortBackup(ctx context.Context) error {
if bh.readOnly {
return errors.New("AbortBackup cannot be called on read-only backup")
}
return bh.bs.RemoveBackup(ctx, bh.dir, bh.name)
}
// ReadFile is part of the backupstorage.BackupHandle interface.
func (bh *S3BackupHandle) ReadFile(ctx context.Context, filename string) (io.ReadCloser, error) {
if !bh.readOnly {
return nil, errors.New("ReadFile cannot be called on read-write backup")
}View on GitHub (pinned to 01a25a7d17)
Solutions
- Only call EndBackup on handles obtained from StartBackup (writable mode).
- Guard cleanup code: check readOnly mode (or track handle kind) before invoking EndBackup.
- For read-only handles, use AbortBackup/Close semantics or simply release resources without finalizing.
- In deferred finalization, capture the writable handle in a variable that is nil for read paths and skip EndBackup when nil.
Example fix
// before
defer func() {
if err := bh.EndBackup(ctx); err != nil { ... }
}()
// after
defer func() {
if bh.ReadOnly() { // or track writable handle separately
return
}
if err := bh.EndBackup(ctx); err != nil { ... }
}() Defensive patterns
Strategy: validation
Validate before calling
if bh.ReadOnly() {
// nothing to finalize on a read-only handle
return nil
}
Try / catch
err := bh.EndBackup(ctx)
if err != nil {
if strings.Contains(err.Error(), "read-only backup") {
return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "EndBackup called on read-only handle; use StartBackup handles for writes")
}
return err
} Prevention
- Track which handle came from StartBackup and only finalize that one.
- Avoid unconditional deferred EndBackup calls on handles that may be read-only.
- Use AbortBackup/close semantics for read-only handles instead of EndBackup.
When it happens
Trigger: Calling S3BackupHandle.EndBackup on a read-only handle (bh.readOnly == true) — e.g. after listing/restoring a backup — immediately errors with "EndBackup cannot be called on read-only backup".
Common situations: Restore or listing code calling EndBackup defensively in a cleanup path on the wrong handle type; mixing up handles between backup and restore flows; wrapping EndBackup in defer without checking handle mode.
Related errors
- AddFile 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/117c02d71e125057.
Report an issue: GitHub.