{"record":{"id":"ffb8be74a492311f","repo":"vitessio/vitess","slug":"abortbackup-cannot-be-called-on-read-only-backup-ffb8be","errorCode":null,"errorMessage":"AbortBackup cannot be called on read-only backup","messagePattern":"AbortBackup cannot be called on read-only backup","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/gcsbackupstorage/gcs.go","lineNumber":106,"sourceCode":"\tobject := objName(bh.dir, bh.name, filename)\n\treturn bh.client.Bucket(bucket).Object(object).NewWriter(ctx), nil\n}\n\n// Wait implements BackupHandle.\nfunc (bh *GCSBackupHandle) Wait() {}\n\n// EndBackup implements BackupHandle.\nfunc (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","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/gcsbackupstorage/gcs.go#L88-L124","documentation":"GCSBackupHandle.AbortBackup refuses to abort a read-only handle. AbortBackup deletes the backup via RemoveBackup, and read-only handles exist precisely to prevent any mutation of the stored backup, so aborting is denied. A read-only consumer cannot and should not delete a backup it is only reading.","triggerScenarios":"Calling AbortBackup on a GCSBackupHandle opened with readOnly=true, typically in a cleanup/error path shared with the backup-creation flow.","commonSituations":"Restore or verification code reusing a 'defer bh.AbortBackup(ctx)' pattern written for the writer side; permission-restricted tooling that opens read-only but tries to clean up on failure.","solutions":["Never call AbortBackup on read-only handles; to delete a backup use the storage-level RemoveBackup with a read-write-capable context and permissions","In shared cleanup paths, check readOnly before choosing between AbortBackup and plain close","If backup deletion is intended, obtain the handle via the write/management API instead of the read-only open"],"exampleFix":"// before\nbh := bs.StartBackup(ctx, dir, name, true /* readOnly */)\nerr := bh.AbortBackup(ctx) // error\n// after\nif !bh.readOnly {\n    err := bh.AbortBackup(ctx)\n} else {\n    // nothing to abort; just close open files\n}","handlingStrategy":"validation","validationCode":"if bh.readOnly {\n    return errors.New(\"cannot AbortBackup on a read-only handle; use RemoveBackup with proper permissions if deletion is intended\")\n}\nerr := bh.AbortBackup(ctx)","typeGuard":null,"tryCatchPattern":"if err := bh.AbortBackup(ctx); err != nil {\n    if strings.Contains(err.Error(), \"AbortBackup cannot be called on read-only backup\") {\n        return nil // nothing to abort\n    }\n    return err\n}","preventionTips":["Guard shared error-path cleanup with a readOnly check","Use storage-level RemoveBackup (with write permissions) when deletion is truly intended","Never reuse writer cleanup logic verbatim in restore code"],"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"}