{"record":{"id":"9d566fde59233d42","repo":"gitbutlerapp/gitbutler","slug":"unconflicted-stage-for-rela-path-is-unexpected","errorCode":null,"errorMessage":"Unconflicted stage for '{rela_path}' is unexpected","messagePattern":"Unconflicted stage for '(.+?)' is unexpected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-core/src/snapshot/resolve_tree.rs","lineNumber":161,"sourceCode":"        let mut recorder = gix::traverse::tree::Recorder::default();\n        conflict_tree.traverse().depthfirst(&mut recorder)?;\n\n        let mut to_remove = BTreeSet::new();\n        for record in &recorder.records {\n            if record.mode.is_tree() {\n                continue;\n            }\n            let rela_path = &record.filepath;\n            let rslash_pos = rela_path\n                .rfind(\"/\")\n                .context(\"BUG: expecting <path>/<stage>\")?;\n            let stage: usize = rela_path[rslash_pos + 1..]\n                .to_str()?\n                .parse()\n                .context(\"Failed to parse stage that should only be [1,2,3]\")?;\n            let rela_path = rela_path[..rslash_pos].as_bstr();\n            let stage = match stage {\n                0 => bail!(\"Unconflicted stage for '{rela_path}' is unexpected\"),\n                1 => Stage::Base,\n                2 => Stage::Ours,\n                3 => Stage::Theirs,\n                other => bail!(\"Invalid stage '{other}' for '{rela_path}'\"),\n            };\n\n            index.dangerously_push_entry(\n                Default::default(),\n                record.oid,\n                Flags::from_stage(stage),\n                record.mode.into(),\n                rela_path,\n            );\n\n            to_remove.insert(rela_path);\n        }\n        index.remove_entries(|_idx, path, entry| {\n            entry.flags.stage() == Stage::Unconflicted && to_remove.contains(path)","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-core/src/snapshot/resolve_tree.rs#L143-L179","documentation":"Snapshot trees encode index conflicts as entries under '<path>/<stage>'. Stage 0 means the file is not conflicted (a single stage-0 entry is normal, not a conflict stage), so encountering an encoded stage 0 is an invariant violation and the resolver bails.","triggerScenarios":"Resolving 'index-conflicts' whose tree contains an entry 'rela_path/0', i.e. a path recorded with conflict stage 0.","commonSituations":"Snapshot written by buggy code that pushed unconflicted entries into the conflicts tree; manual test fixtures using stage 0; corrupted snapshot data.","solutions":["Fix the snapshot writer to never store stage 0 in the index-conflicts tree","Remove or correct the offending '<path>/0' entry and rebuild the snapshot","Treat stage 0 entries as plain index entries rather than conflicts in the writer"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// when writing snapshot conflicts, skip unconflicted entries\nif entry.stage() != 0 { push_conflict_entry(entry)?; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only store stages 1-3 in the index-conflicts tree","Filter stage-0 entries out of conflict snapshots","Test round-tripping conflicted and unconflicted indexes through snapshots"],"tags":["rust","git","index","conflict","validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}