{"record":{"id":"73562447233627b1","repo":"kopia/kopia","slug":"blob-retention-735624","errorCode":null,"errorMessage":"blob-retention","messagePattern":"blob-retention","errorType":"exception","errorClass":"blob.ErrUnsupportedPutBlobOption","httpStatus":null,"severity":"error","filePath":"repo/blob/sftp/sftp_storage.go","lineNumber":196,"sourceCode":"\t\t}\n\n\t\tif err != nil {\n\t\t\treturn blob.Metadata{}, errors.Wrapf(err, \"unrecognized error when calling stat() on SFTP file %v\", fullPath)\n\t\t}\n\n\t\treturn blob.Metadata{\n\t\t\tLength:    fi.Size(),\n\t\t\tTimestamp: fi.ModTime(),\n\t\t}, nil\n\t})\n}\n\nfunc (s *sftpImpl) PutBlobInPath(ctx context.Context, dirPath, fullPath string, data blob.Bytes, opts blob.PutOptions) error {\n\t_ = dirPath\n\n\tswitch {\n\tcase opts.HasRetentionOptions():\n\t\treturn errors.Wrap(blob.ErrUnsupportedPutBlobOption, \"blob-retention\")\n\tcase opts.DoNotRecreate:\n\t\treturn errors.Wrap(blob.ErrUnsupportedPutBlobOption, \"do-not-recreate\")\n\t}\n\n\t// SFTP client Write() does not do any buffering leading to sub-optimal\n\t// performance of gather writes, so we copy the data to a contiguous\n\t// temporary buffer first.\n\tcontig := gather.NewWriteBufferMaxContiguous()\n\tdefer contig.Close()\n\n\tif _, err := data.WriteTo(contig); err != nil {\n\t\treturn errors.Wrap(err, \"can't write to contiguous buffer\")\n\t}\n\n\t//nolint:wrapcheck\n\treturn s.rec.UsingConnectionNoResult(ctx, \"PutBlobInPath\", func(conn connection.Connection) error {\n\t\trandSuffix := make([]byte, tempFileRandomSuffixLen)\n\t\tif _, err := rand.Read(randSuffix); err != nil {","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/kopia/kopia/blob/82495e54b584c1ef6073c9e1be048f57f8aef078/repo/blob/sftp/sftp_storage.go#L178-L214","documentation":"A static sentinel-style wrap indicating that the SFTP storage backend does not support put-blob retention options. sftpImpl.PutBlobInPath rejects PutOptions carrying retention settings because SFTP has no object-lock/retention mechanism.","triggerScenarios":"PutBlobInPath (or PutBlob) is called with blob.PutOptions where HasRetentionOptions() is true — e.g. callers setting retention mode/retain-until designed for S3 object lock.","commonSituations":"Code shared between S3 and SFTP backends passing the same PutOptions to both; enabling retention/immutable-backup features while using the SFTP provider; misconfigured storage profile requesting retention on a non-supporting backend.","solutions":["Remove retention options from PutOptions when writing to SFTP storage.","Route blobs that require retention to an S3 backend with object lock enabled instead.","Check upstream caller/provider config so retention options are only set for supporting backends.","If retention is required on SFTP, implement it externally (e.g. filesystem ACLs/cron cleanup) — the library cannot honor it."],"exampleFix":"// before\nerr := st.PutBlob(ctx, blobID, data, blob.PutOptions{Retention: blob.RetentionOptions{...}})\n// after\nopts := blob.PutOptions{}\nif s3Backend { opts.Retention = blob.RetentionOptions{...} }\nerr := st.PutBlob(ctx, blobID, data, opts)","handlingStrategy":"validation","validationCode":"if opts.HasRetentionOptions() {\n    return fmt.Errorf(\"retention options are not supported by the SFTP backend\")\n}\n// call PutBlob only if validation passes","typeGuard":"null","tryCatchPattern":"if err := st.PutBlob(ctx, id, data, opts); err != nil {\n    if strings.Contains(err.Error(), \"blob-retention\") { /* strip retention opts or use S3 backend */ }\n}","preventionTips":["Only set PutOptions.Retention when the active backend supports it (S3 with object lock).","Centralize PutOptions construction per provider.","Add a config-level check: reject retention/immutable features when storage type is SFTP."],"tags":["sftp","retention","unsupported"],"backgroundTag":"unsupported-operation","analyzedSha":"82495e54b584c1ef6073c9e1be048f57f8aef078","analyzedAt":"2026-09-07T20:35:21.689Z","contentChangedAt":"2026-09-07T20:35:21.689Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}