kopia/kopia · error · blob.ErrUnsupportedPutBlobOption
blob-retention
Error message
blob-retention
What it means
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.
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.
Example fix
// before
err := st.PutBlob(ctx, blobID, data, blob.PutOptions{Retention: blob.RetentionOptions{...}})
// after
opts := blob.PutOptions{}
if s3Backend { opts.Retention = blob.RetentionOptions{...} }
err := st.PutBlob(ctx, blobID, data, opts) Defensive patterns
Strategy: validation
Validate before calling
if opts.HasRetentionOptions() {
return fmt.Errorf("retention options are not supported by the SFTP backend")
}
// call PutBlob only if validation passes Type guard
null
Try / catch
if err := st.PutBlob(ctx, id, data, opts); err != nil {
if strings.Contains(err.Error(), "blob-retention") { /* strip retention opts or use S3 backend */ }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- blob configuration
- blob.ErrUnsupportedPutBlobOption
- blob-retention
- blob-retention
- both retention mode and period must be provided when…
AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07).
Data as JSON: /api/errors/73562447233627b1.
Report an issue: GitHub.
Appendix: source
Thrown at repo/blob/sftp/sftp_storage.go:196
}
if err != nil {
return blob.Metadata{}, errors.Wrapf(err, "unrecognized error when calling stat() on SFTP file %v", fullPath)
}
return blob.Metadata{
Length: fi.Size(),
Timestamp: fi.ModTime(),
}, nil
})
}
func (s *sftpImpl) PutBlobInPath(ctx context.Context, dirPath, fullPath string, data blob.Bytes, opts blob.PutOptions) error {
_ = dirPath
switch {
case opts.HasRetentionOptions():
return errors.Wrap(blob.ErrUnsupportedPutBlobOption, "blob-retention")
case opts.DoNotRecreate:
return errors.Wrap(blob.ErrUnsupportedPutBlobOption, "do-not-recreate")
}
// SFTP client Write() does not do any buffering leading to sub-optimal
// performance of gather writes, so we copy the data to a contiguous
// temporary buffer first.
contig := gather.NewWriteBufferMaxContiguous()
defer contig.Close()
if _, err := data.WriteTo(contig); err != nil {
return errors.Wrap(err, "can't write to contiguous buffer")
}
//nolint:wrapcheck
return s.rec.UsingConnectionNoResult(ctx, "PutBlobInPath", func(conn connection.Connection) error {
randSuffix := make([]byte, tempFileRandomSuffixLen)
if _, err := rand.Read(randSuffix); err != nil {View on GitHub (pinned to 82495e54b5)