{"record":{"id":"c918fc332c890df5","repo":"vitessio/vitess","slug":"addfile-cannot-be-called-on-read-only-backup-c918fc","errorCode":null,"errorMessage":"AddFile cannot be called on read-only backup","messagePattern":"AddFile cannot be called on read-only backup","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/s3backupstorage/s3.go","lineNumber":226,"sourceCode":"\treadOnly  bool\n\twaitGroup sync.WaitGroup\n\terrorsbackup.PerFileErrorRecorder\n}\n\n// Directory is part of the backupstorage.BackupHandle interface.\nfunc (bh *S3BackupHandle) Directory() string {\n\treturn bh.dir\n}\n\n// Name is part of the backupstorage.BackupHandle interface.\nfunc (bh *S3BackupHandle) Name() string {\n\treturn bh.name\n}\n\n// AddFile is part of the backupstorage.BackupHandle interface.\nfunc (bh *S3BackupHandle) AddFile(ctx context.Context, filename string, filesize int64) (io.WriteCloser, error) {\n\tif bh.readOnly {\n\t\treturn nil, errors.New(\"AddFile cannot be called on read-only backup\")\n\t}\n\n\tpartSizeBytes, err := calculateUploadPartSize(filesize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbh.bs.params.Logger.Infof(\"Using S3 upload part size: %s\", humanize.IBytes(uint64(partSizeBytes)))\n\n\treader, writer := io.Pipe()\n\tbh.handleAddFile(ctx, filename, partSizeBytes, reader, func(err error) {\n\t\treader.CloseWithError(err)\n\t})\n\n\treturn writer, nil\n}\n\nfunc (bh *S3BackupHandle) handleAddFile(ctx context.Context, filename string, partSizeBytes int64, reader io.Reader, closer func(error)) {","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/s3backupstorage/s3.go#L208-L244","documentation":"S3BackupHandle.AddFile refuses to open a new file for writing when the handle was created read-only (bh.readOnly). Read-only handles are for listing/downloading existing backups; writing would corrupt the invariant that read-only handles never mutate backup state. This is an API misuse guard, not a storage failure.","triggerScenarios":"Calling S3BackupHandle.AddFile on a handle obtained via a read-only constructor/path (e.g. listing or restoring a backup), where bh.readOnly is true — immediately returns \"AddFile cannot be called on read-only backup\".","commonSituations":"Restore code accidentally using the read-only handle from ListBackups instead of opening a writable handle; copy/paste between backup (write) and restore (read) code paths; attempting to append files to an already-completed backup.","solutions":["Obtain a writable backup handle (StartBackup) instead of a read-only one before calling AddFile.","Check bh.readOnly (or the handle type/mode) before calling AddFile and branch accordingly.","If appending to an existing backup is the goal, that is unsupported — create a new backup instead."],"exampleFix":"// before\nbh, _ := backupstorage.GetBackupHandle()\nroHandle, _ := bmu.ListBackups(ctx) // read-only handle\nroHandle.AddFile(ctx, \"file\", size) // error\n\n// after\nwritableHandle, err := bmu.StartBackup(ctx, backupName)\nif err != nil {\n    return err\n}\nwc, err := writableHandle.AddFile(ctx, \"file\", size)","handlingStrategy":"validation","validationCode":"if bh.ReadOnly() { // or equivalent mode check on the handle\n    return errors.New(\"refusing to AddFile on read-only backup handle\")\n}\n","typeGuard":null,"tryCatchPattern":"wc, err := bh.AddFile(ctx, filename, size)\nif err != nil {\n    if strings.Contains(err.Error(), \"read-only backup\") {\n        return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, \"wrong handle mode: cannot write via read-only backup handle\")\n    }\n    return err\n}","preventionTips":["Keep write flows (backup) and read flows (restore/list) on separate, clearly named handle variables.","Never reuse a handle from ListBackups for writing.","Type or wrap handles so writable vs read-only is visible at the call site."],"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"}