{"record":{"id":"706dac666479f0b0","repo":"vitessio/vitess","slug":"abortbackup-cannot-be-called-on-read-only-backup-706dac","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/s3backupstorage/s3.go","lineNumber":326,"sourceCode":"\n// Wait is part of the backupstorage.BackupHandle interface.\nfunc (bh *S3BackupHandle) Wait() {\n\tbh.waitGroup.Wait()\n}\n\n// EndBackup is part of the backupstorage.BackupHandle interface.\nfunc (bh *S3BackupHandle) 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\tbh.Wait()\n\treturn bh.Error()\n}\n\n// AbortBackup is part of the backupstorage.BackupHandle interface.\nfunc (bh *S3BackupHandle) 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 is part of the backupstorage.BackupHandle interface.\nfunc (bh *S3BackupHandle) 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\tsendStats := bh.bs.params.Stats.Scope(stats.Operation(\"AWS:Request:Send\"))\n\tout, err := (&timedS3Client{client: bh.s3Client, sendStats: sendStats}).GetObject(ctx, &s3.GetObjectInput{\n\t\tBucket:               &bucket,\n\t\tKey:                  &object,\n\t\tSSECustomerAlgorithm: bh.bs.s3SSE.customerAlg,\n\t\tSSECustomerKey:       bh.bs.s3SSE.customerKey,\n\t\tSSECustomerKeyMD5:    bh.bs.s3SSE.customerMd5,\n\t})","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/s3backupstorage/s3.go#L308-L344","documentation":"S3BackupHandle.AbortBackup deletes the backup (RemoveBackup) but is only valid on a handle opened for writing. If the handle was obtained in read-only mode (e.g. via StartBackup's read path or a handle opened to restore/read), the guard in s3.go:326 rejects the call outright because aborting would destroy a backup another process may be reading.","triggerScenarios":"Calling AbortBackup on an *S3BackupHandle that was created with readOnly=true — e.g. grabbing a backup handle to inspect/read contents and then attempting to abort/clean it up instead of just closing it.","commonSituations":"Cleanup logic written against write-mode handles being reused for read-mode handles; a tool that lists backups, opens them read-only, then tries to abort partially-uploaded ones; confusing backupstorage.BackupHandle semantics between reader and writer roles.","solutions":["Only call AbortBackup on handles returned from StartBackup (write mode)","For read-only handles, just call Close() and use RemoveBackup on the storage engine if deletion is truly intended","Re-open the handle in write mode via the storage engine's API if you legitimately own the backup and need to abort","Check bh.readOnly (or your own tracking) before dispatching to AbortBackup"],"exampleFix":"// before\nhandle, _ := bs.StartBackup(ctx, dir, name)\n_ = handle.AbortBackup(ctx) // wrong handle mode\n// after\nif !handle.ReadOnly() {\n    _ = handle.AbortBackup(ctx)\n} else {\n    _ = handle.Close()\n}","handlingStrategy":"validation","validationCode":"if bh.ReadOnly() {\n    // do not abort; close instead\n    return bh.Close()\n}\nreturn bh.AbortBackup(ctx)","typeGuard":"func isWritableHandle(bh backupstorage.BackupHandle) bool {\n    h, ok := bh.(*s3backupstorage.S3BackupHandle)\n    return ok && !h.ReadOnly()\n}","tryCatchPattern":"if err := bh.AbortBackup(ctx); err != nil {\n    if strings.Contains(err.Error(), \"cannot be called on read-only backup\") {\n        err = bh.Close() // correct operation for read-only handles\n    }\n    return err\n}","preventionTips":["Track handle mode (read vs write) in your backup tooling and branch on it","Only call AbortBackup on handles obtained from StartBackup","Prefer Close() for read handles; reserve RemoveBackup for deliberate deletion","Add unit tests asserting handle-mode guards"],"tags":["s3","backup","api-misuse","go"],"backgroundTag":"invalid-backup-handle-state","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}