{"record":{"id":"9e56d783c4d03969","repo":"vitessio/vitess","slug":"readfile-cannot-be-called-on-read-write-backup-9e56d7","errorCode":null,"errorMessage":"ReadFile cannot be called on read-write backup","messagePattern":"ReadFile cannot be called on read-write backup","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/gcsbackupstorage/gcs.go","lineNumber":114,"sourceCode":"func (bh *GCSBackupHandle) EndBackup(ctx context.Context) error {\n\tif bh.readOnly {\n\t\treturn errors.New(\"EndBackup cannot be called on read-only backup\")\n\t}\n\treturn nil\n}\n\n// AbortBackup implements BackupHandle.\nfunc (bh *GCSBackupHandle) AbortBackup(ctx context.Context) error {\n\tif bh.readOnly {\n\t\treturn errors.New(\"AbortBackup cannot be called on read-only backup\")\n\t}\n\treturn bh.bs.RemoveBackup(ctx, bh.dir, bh.name)\n}\n\n// ReadFile implements BackupHandle.\nfunc (bh *GCSBackupHandle) ReadFile(ctx context.Context, filename string) (io.ReadCloser, error) {\n\tif !bh.readOnly {\n\t\treturn nil, errors.New(\"ReadFile cannot be called on read-write backup\")\n\t}\n\tobject := objName(bh.dir, bh.name, filename)\n\treturn bh.client.Bucket(bucket).Object(object).NewReader(ctx)\n}\n\n// GCSBackupStorage implements BackupStorage for Google Cloud Storage.\ntype GCSBackupStorage struct {\n\t// client is the instance of the Google Cloud Storage Go client.\n\t// Once this field is set, it must not be written again/unset to nil.\n\t_client *storage.Client\n\t// mu guards all fields.\n\tmu sync.Mutex\n}\n\n// ListBackups implements BackupStorage.\nfunc (bs *GCSBackupStorage) ListBackups(ctx context.Context, dir string) ([]backupstorage.BackupHandle, error) {\n\tc, err := bs.client(ctx)\n\tif err != nil {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/gcsbackupstorage/gcs.go#L96-L132","documentation":"GCSBackupHandle.ReadFile requires a read-only handle; it is the mirror guard of AddFile's. Reads are only permitted on handles opened read-only so that restore/inspection traffic can be distinguished from backup writing, and a read-write handle cannot be used to stream files back.","triggerScenarios":"Calling ReadFile on a GCSBackupHandle created for backup writing (readOnly=false, e.g. from StartBackup with readOnly=false).","commonSituations":"Verification code that opens a handle to write and then tries to read back what it wrote; generic helper functions assuming one handle serves both directions.","solutions":["Open a second handle with readOnly=true for any ReadFile calls","Complete the write phase, then re-open the backup read-only to verify contents","Check the handle's readOnly mode before dispatching read vs write operations"],"exampleFix":"// before\nbh := bs.StartBackup(ctx, dir, name, false)\nr, err := bh.ReadFile(ctx, \"backupinfo\") // error\n// after\nrh := bs.StartBackup(ctx, dir, name, true /* readOnly */)\nr, err := rh.ReadFile(ctx, \"backupinfo\")","handlingStrategy":"validation","validationCode":"if bh.readOnly == false {\n    return errors.New(\"ReadFile requires a read-only handle; re-open the backup read-only\")\n}\nr, err := bh.ReadFile(ctx, filename)","typeGuard":null,"tryCatchPattern":"r, err := bh.ReadFile(ctx, filename)\nif err != nil {\n    if strings.Contains(err.Error(), \"ReadFile cannot be called on read-write backup\") {\n        // obtain a readOnly handle and retry\n    }\n    return err\n}","preventionTips":["Verify backup contents via a separate read-only handle after the write phase completes","Keep verification helpers taking read-only handles only"],"tags":["go","mysqlctl","gcs","backup-storage","api-misuse"],"backgroundTag":"backup-handle-readonly-violation","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}