{"record":{"id":"7f680dc02e19ccb8","repo":"vitessio/vitess","slug":"readfile-cannot-be-called-on-read-write-backup-7f680d","errorCode":null,"errorMessage":"ReadFile cannot be called on read-write backup","messagePattern":"ReadFile cannot be called on read-write backup","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/s3backupstorage/s3.go","lineNumber":334,"sourceCode":"\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})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out.Body, nil\n}\n\nvar _ backupstorage.BackupHandle = (*S3BackupHandle)(nil)\n","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/s3backupstorage/s3.go#L316-L352","documentation":"S3BackupHandle.ReadFile downloads an object from the backup and is only permitted on read-only handles. On a handle opened for writing (from StartBackup), reading is disallowed because the write session's semantics don't guarantee the object exists or is final, so the guard at s3.go:334 fails fast.","triggerScenarios":"Calling ReadFile on the handle returned by StartBackup (read-write mode) instead of on a handle opened for reading an existing backup.","commonSituations":"Restore tooling that accidentally uses the writer handle; code that starts a backup, then tries to re-read the manifest from the same handle; copy/pasted handle usage between backup and restore paths.","solutions":["Open a read-only handle for the existing backup and call ReadFile on that","Use AddFile/Close on write handles; reserve ReadFile for read handles","Restructure code so backup writing and manifest reading use separate handles","Assert readOnly before calling ReadFile"],"exampleFix":"// before\nbh, _ := bs.StartBackup(ctx, dir, name)\nr, err := bh.ReadFile(ctx, \"manifest.json\")\n// after\nbh, _ := bs.StartBackup(ctx, dir, name)\n// ... write files, then use a read handle for verification\nrbh, _ := bs.StartBackup(ctx, dir, name) // or storage-engine read API with readOnly\nr, err := rbh.ReadFile(ctx, \"manifest.json\")","handlingStrategy":"validation","validationCode":"if !bh.ReadOnly() {\n    return fmt.Errorf(\"ReadFile requires a read-only handle\")\n}\nr, err := bh.ReadFile(ctx, filename)","typeGuard":"func canRead(bh backupstorage.BackupHandle) bool {\n    h, ok := bh.(*s3backupstorage.S3BackupHandle)\n    return ok && h.ReadOnly()\n}","tryCatchPattern":"rc, err := bh.ReadFile(ctx, filename)\nif err != nil && strings.Contains(err.Error(), \"cannot be called on read-write backup\") {\n    return fmt.Errorf(\"use a read handle to read %s: %w\", filename, err)\n}","preventionTips":["Keep writer handles for AddFile only; open separate read handles for verification","Name variables to encode handle mode (wbh vs rbh)","Review backup vs restore code paths for handle crossover","Wrap handle usage in small helpers that encode the mode in the API"],"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"}