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

  1. Only call EndBackup on handles obtained from StartBackup (writable mode).
  2. Guard cleanup code: check readOnly mode (or track handle kind) before invoking EndBackup.
  3. For read-only handles, use AbortBackup/Close semantics or simply release resources without finalizing.
  4. 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

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


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