{"record":{"id":"c139e2951c4b9de6","repo":"gitbutlerapp/gitbutler","slug":"invalid-trailer-format-expected-key-value","errorCode":null,"errorMessage":"Invalid trailer format, expected `key: value`","messagePattern":"Invalid trailer format, expected `key: value`","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-core/src/snapshot/mod.rs","lineNumber":57,"sourceCode":"        pub key: String,\n        /// Trailer value.\n        pub value: String,\n    }\n\n    impl Display for CommitTrailer {\n        fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n            let escaped_value = self.value.replace('\\n', \"\\\\n\");\n            write!(f, \"{}: {}\", self.key, escaped_value)\n        }\n    }\n\n    impl FromStr for CommitTrailer {\n        type Err = anyhow::Error;\n\n        fn from_str(s: &str) -> anyhow::Result<Self, Self::Err> {\n            let mut parts = s.splitn(2, ':');\n            let (Some(key), Some(value)) = (parts.next(), parts.next()) else {\n                return Err(anyhow!(\"Invalid trailer format, expected `key: value`\"));\n            };\n            let unescaped_value = value.trim().replace(\"\\\\n\", \"\\n\");\n            Ok(Self {\n                key: key.trim().to_string(),\n                value: unescaped_value,\n            })\n        }\n    }\n\n    /// Metadata attached to [`Commit`]s holding snapshots.\n    pub struct CommitMetadata {\n        /// The name of the operation that created the commit.\n        /// This is an internal string.\n        pub operation: String,\n        /// The title of the commit for user consumption, typically created using information from `trailers`.\n        pub title: String,\n        /// Properties to be stored with the commit.\n        pub trailers: Vec<CommitTrailer>,","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but-core/src/snapshot/mod.rs#L39-L75","documentation":"CommitTrailer::from_str parses one serialized trailer line and requires a colon separating key from value (it splits on the first ':'). Input without any colon — a bare word or a key that lost its ': value' suffix — fails with this message. 'key:' with an empty value is accepted, and newlines inside values must be escaped as literal \\n, so only colon-less strings are rejected.","triggerScenarios":"Deserializing CommitMetadata trailers where one line is malformed: 'Co-authored-by' without '<email>', a line of free text slipping into the trailers list, or concatenation bugs that dropped the colon.","commonSituations":"Hand-edited snapshot/metadata files; trailers split on the wrong delimiter; migration code writing raw multi-line text instead of escaped 'key: value' pairs.","solutions":["Validate each line contains ':' (split_once) before parsing; reject or skip bad lines with a clear index.","Fix the producer to always emit 'key: value' with \\n-escaped newlines in values.","When accepting user input, pre-check with a pattern like ^[^:]+: ."],"exampleFix":"// before\nlet trailer: CommitTrailer = line.parse()?;\n\n// after\nanyhow::ensure!(\n    line.split_once(':').is_some(),\n    \"malformed trailer line (expected `key: value`): {line:?}\"\n);\nlet trailer: CommitTrailer = line.parse()?;","handlingStrategy":"validation","validationCode":"for (i, line) in lines.iter().enumerate() {\n    if line.split_once(':').is_none() {\n        anyhow::bail!(\"trailer[{i}] is not `key: value`: {line:?}\");\n    }\n}","typeGuard":"fn is_trailer_line(s: &str) -> bool {\n    s.split_once(':').is_some()\n}","tryCatchPattern":null,"preventionTips":["Write trailers with a single serializer that always formats 'key: value'.","Escape newlines as \\n when serializing values; unescape only after a successful parse.","Reject free-text lines before they reach the trailer parser."],"tags":["parsing","trailers","metadata","format","serde"],"backgroundTag":"key-value-parse-error","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}