{"record":{"id":"117c02d71e125057","repo":"vitessio/vitess","slug":"endbackup-cannot-be-called-on-read-only-backup-117c02","errorCode":null,"errorMessage":"EndBackup cannot be called on read-only backup","messagePattern":"EndBackup cannot be called on read-only backup","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/s3backupstorage/s3.go","lineNumber":317,"sourceCode":"\t\t\t\t\"%w, currently set to %s\",\n\t\t\t\tErrPartSize, humanize.IBytes(uint64(minPartSize)),\n\t\t\t)\n\t\t}\n\t\tpartSizeBytes = int64(minPartSize)\n\t}\n\n\treturn\n}\n\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}","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/s3backupstorage/s3.go#L299-L335","documentation":"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.","triggerScenarios":"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\".","commonSituations":"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.","solutions":["Only call EndBackup on handles obtained from StartBackup (writable mode).","Guard cleanup code: check readOnly mode (or track handle kind) before invoking EndBackup.","For read-only handles, use AbortBackup/Close semantics or simply release resources without finalizing.","In deferred finalization, capture the writable handle in a variable that is nil for read paths and skip EndBackup when nil."],"exampleFix":"// before\ndefer func() {\n    if err := bh.EndBackup(ctx); err != nil { ... }\n}()\n\n// after\ndefer func() {\n    if bh.ReadOnly() { // or track writable handle separately\n        return\n    }\n    if err := bh.EndBackup(ctx); err != nil { ... }\n}()","handlingStrategy":"validation","validationCode":"if bh.ReadOnly() {\n    // nothing to finalize on a read-only handle\n    return nil\n}\n","typeGuard":null,"tryCatchPattern":"err := bh.EndBackup(ctx)\nif err != nil {\n    if strings.Contains(err.Error(), \"read-only backup\") {\n        return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, \"EndBackup called on read-only handle; use StartBackup handles for writes\")\n    }\n    return err\n}","preventionTips":["Track which handle came from StartBackup and only finalize that one.","Avoid unconditional deferred EndBackup calls on handles that may be read-only.","Use AbortBackup/close semantics for read-only handles instead of EndBackup."],"tags":["s3","backup","api-misuse","read-only"],"backgroundTag":"read-only-handle-misuse","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}