vitessio/vitess · error

ReadFile cannot be called on read-write backup

Error message

ReadFile cannot be called on read-write backup

What it means

S3BackupHandle.ReadFile downloads an object from the backup and is only permitted on read-only handles. On a handle opened for writing (from StartBackup), reading is disallowed because the write session's semantics don't guarantee the object exists or is final, so the guard at s3.go:334 fails fast.

Source

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

	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")
	}
	object := objName(bh.dir, bh.name, filename)
	sendStats := bh.bs.params.Stats.Scope(stats.Operation("AWS:Request:Send"))
	out, err := (&timedS3Client{client: bh.s3Client, sendStats: sendStats}).GetObject(ctx, &s3.GetObjectInput{
		Bucket:               &bucket,
		Key:                  &object,
		SSECustomerAlgorithm: bh.bs.s3SSE.customerAlg,
		SSECustomerKey:       bh.bs.s3SSE.customerKey,
		SSECustomerKeyMD5:    bh.bs.s3SSE.customerMd5,
	})
	if err != nil {
		return nil, err
	}
	return out.Body, nil
}

var _ backupstorage.BackupHandle = (*S3BackupHandle)(nil)

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Open a read-only handle for the existing backup and call ReadFile on that
  2. Use AddFile/Close on write handles; reserve ReadFile for read handles
  3. Restructure code so backup writing and manifest reading use separate handles
  4. Assert readOnly before calling ReadFile

Example fix

// before
bh, _ := bs.StartBackup(ctx, dir, name)
r, err := bh.ReadFile(ctx, "manifest.json")
// after
bh, _ := bs.StartBackup(ctx, dir, name)
// ... write files, then use a read handle for verification
rbh, _ := bs.StartBackup(ctx, dir, name) // or storage-engine read API with readOnly
r, err := rbh.ReadFile(ctx, "manifest.json")
Defensive patterns

Strategy: validation

Validate before calling

if !bh.ReadOnly() {
    return fmt.Errorf("ReadFile requires a read-only handle")
}
r, err := bh.ReadFile(ctx, filename)

Type guard

func canRead(bh backupstorage.BackupHandle) bool {
    h, ok := bh.(*s3backupstorage.S3BackupHandle)
    return ok && h.ReadOnly()
}

Try / catch

rc, err := bh.ReadFile(ctx, filename)
if err != nil && strings.Contains(err.Error(), "cannot be called on read-write backup") {
    return fmt.Errorf("use a read handle to read %s: %w", filename, err)
}

Prevention

When it happens

Trigger: Calling ReadFile on the handle returned by StartBackup (read-write mode) instead of on a handle opened for reading an existing backup.

Common situations: Restore tooling that accidentally uses the writer handle; code that starts a backup, then tries to re-read the manifest from the same handle; copy/pasted handle usage between backup and restore paths.

Related errors


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