{"record":{"id":"e1a62860e0d4cf7f","repo":"rustfs/rustfs","slug":"only-one-transition-action-per-lifecycle-rule-is-s","errorCode":null,"errorMessage":"Only one Transition action per lifecycle rule is supported","messagePattern":"Only one Transition action per lifecycle rule is supported","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/lifecycle/src/core.rs","lineNumber":561,"sourceCode":"            // integer (>= 1); AWS rejects 0 with InvalidArgument.\n            if let Some(noncurrent_version_expiration) = &r.noncurrent_version_expiration\n                && let Some(noncurrent_days) = noncurrent_version_expiration.noncurrent_days\n                && noncurrent_days < 1\n            {\n                return Err(std::io::Error::other(ERR_LIFECYCLE_INVALID_NONCURRENT_EXPIRATION_DAYS));\n            }\n            // S3 requires AbortIncompleteMultipartUpload.DaysAfterInitiation to be present\n            // and a positive integer (>= 1); AWS rejects 0 (and a missing value) with\n            // InvalidArgument.\n            if let Some(abort_incomplete_multipart_upload) = &r.abort_incomplete_multipart_upload {\n                match abort_incomplete_multipart_upload.days_after_initiation {\n                    Some(days) if days >= 1 => {}\n                    _ => return Err(std::io::Error::other(ERR_LIFECYCLE_INVALID_ABORT_INCOMPLETE_MPU_DAYS)),\n                }\n            }\n            if let Some(transitions) = &r.transitions {\n                if transitions.len() > 1 {\n                    return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, ERR_LIFECYCLE_MULTIPLE_TRANSITIONS));\n                }\n                for transition in transitions {\n                    TransitionOps::validate(transition)?;\n                }\n            }\n            if let Some(noncurrent_transitions) = &r.noncurrent_version_transitions {\n                if noncurrent_transitions.len() > 1 {\n                    return Err(std::io::Error::new(\n                        std::io::ErrorKind::InvalidInput,\n                        ERR_LIFECYCLE_MULTIPLE_NONCURRENT_TRANSITIONS,\n                    ));\n                }\n                for transition in noncurrent_transitions {\n                    NoncurrentVersionTransitionOps::validate(transition)?;\n                }\n            }\n            if let Some(id) = &r.id {\n                if id.is_empty() {","sourceCodeStart":543,"sourceCodeEnd":579,"githubUrl":"https://github.com/rustfs/rustfs/blob/5dca076efed96e7b842de07c4c2111035ae7c7a2/crates/lifecycle/src/core.rs#L543-L579","documentation":"Thrown during lifecycle rule validation when a rule carries more than one Transition action. The server restricts each lifecycle rule to at most one Transition, matching the S3 single-transition-per-rule model. The error is an io::Error with ErrorKind::InvalidInput and message ERR_LIFECYCLE_MULTIPLE_TRANSITIONS.","triggerScenarios":"Calling validate (lifecycle PUT/import) on a rule where the transitions array has len() > 1, e.g. two Transition entries with different storage classes or days.","commonSituations":"Merging lifecycle policies from multiple clouds (AWS console exports normally split transitions into separate rules); bulk config generators that append a transition per storage class; converting JSONata/manual policies that allowed multiple transitions.","solutions":["Split the rule so each Transition lives in its own lifecycle rule with an identical filter","Keep only the single highest-priority transition in this rule","Check transitions.len() <= 1 before submitting the configuration"],"exampleFix":"// before\n{ \"Rules\": [{ \"Transitions\": [{\"Days\":30,\"StorageClass\":\"GLACIER\"},{\"Days\":60,\"StorageClass\":\"DEEP_ARCHIVE\"}] }]\n// after\n{ \"Rules\": [\n  { \"ID\":\"t1\", \"Transitions\": [{\"Days\":30,\"StorageClass\":\"GLACIER\"}] },\n  { \"ID\":\"t2\", \"Transitions\": [{\"Days\":60,\"StorageClass\":\"DEEP_ARCHIVE\"}] }\n]}","handlingStrategy":"validation","validationCode":"fn single_transition(r: &Rule) -> bool { r.transitions.as_ref().map_or(true, |t| t.len() <= 1) }","typeGuard":null,"tryCatchPattern":"match config.validate() { Err(e) if e.to_string().contains(\"Only one Transition\") => split rule and retry, ... }","preventionTips":["Generate one rule per Transition when exporting from other clouds","Assert transitions length before PUT","Keep transition tiering in dedicated rules with copied filters"],"tags":["rust","lifecycle","s3","validation"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"5dca076efed96e7b842de07c4c2111035ae7c7a2","analyzedAt":"2026-09-06T05:54:05.891Z","contentChangedAt":"2026-09-06T05:54:05.891Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}