{"record":{"id":"4ce44ee0a62273df","repo":"vitessio/vitess","slug":"addfile-cannot-be-called-on-read-only-backup-4ce44e","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/gcsbackupstorage/gcs.go","lineNumber":86,"sourceCode":"\tname     string\n\treadOnly bool\n\tmysqlctlerrors.PerFileErrorRecorder\n}\n\n// Directory implements BackupHandle.\nfunc (bh *GCSBackupHandle) Directory() string {\n\treturn bh.dir\n}\n\n// Name implements BackupHandle.\nfunc (bh *GCSBackupHandle) Name() string {\n\treturn bh.name\n}\n\n// AddFile implements BackupHandle.\nfunc (bh *GCSBackupHandle) 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\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 {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/gcsbackupstorage/gcs.go#L68-L104","documentation":"GCSBackupHandle.AddFile rejects writes to a backup handle opened read-only. Read-only handles represent backups opened for inspection or restore; adding files to them is not permitted and returns this error. It enforces that backup contents are immutable once a backup is finalized.","triggerScenarios":"Calling AddFile on a GCSBackupHandle created with readOnly=true (typical of restore/verification flows that open existing GCS backups read-only).","commonSituations":"Backup-verification or restore code accidentally invoking the write path; sharing a handle between a read pass and a write pass; copying logic written for a fresh-backup handle onto a read-only handle.","solutions":["Use a read-write handle (StartBackup/CreateBackup path) when you need AddFile","Do not call AddFile on existing backups; create a new backup instead of mutating a finished one","Check the handle's readOnly flag before branching into write calls"],"exampleFix":"// before\nbh := bs.StartBackup(ctx, dir, name, true /* readOnly */)\nw, err := bh.AddFile(ctx, \"binlog.pos\", 64) // error\n// after\nbh := bs.StartBackup(ctx, dir, name, false)\nw, err := bh.AddFile(ctx, \"binlog.pos\", 64)","handlingStrategy":"validation","validationCode":"if bh.readOnly {\n    return errors.New(\"cannot AddFile on a read-only GCS backup handle\")\n}\nw, err := bh.AddFile(ctx, filename, filesize)","typeGuard":null,"tryCatchPattern":"w, err := bh.AddFile(ctx, filename, size)\nif err != nil {\n    if strings.Contains(err.Error(), \"AddFile cannot be called on read-only backup\") {\n        return fmt.Errorf(\"%w: reopen the backup for writing\", err)\n    }\n    return err\n}","preventionTips":["Only request write handles when creating a new backup","Never mutate finalized backups; create a new backup instead","Branch write calls on the handle's readOnly mode"],"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"}