{"record":{"id":"c41672af2c9bdf3d","repo":"gitbutlerapp/gitbutler","slug":"invalid-stage-other-for-rela-path","errorCode":null,"errorMessage":"Invalid stage '{other}' for '{rela_path}'","messagePattern":"Invalid stage '(.+?)' for '(.+?)'","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-core/src/snapshot/resolve_tree.rs","lineNumber":165,"sourceCode":"        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)\n        });\n\n        index.sort_entries();\n        Ok(())","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-core/src/snapshot/resolve_tree.rs#L147-L183","documentation":"Thrown while but-core reconstructs a conflicted git index from a snapshot tree. The snapshot's 'index-conflicts' subtree stores each unmerged entry as a record whose path is `<path>/<stage>`, and git only defines stages 1=base, 2=ours, 3=theirs (0 is unconflicted and is rejected just above). This error fires when the trailing number parses as an integer but is 4 or higher - a stage stock git never writes, so the stored snapshot data itself is bad.","triggerScenarios":"Calling crate::snapshot::resolve_tree on a snapshot tree whose 'index-conflicts' tree contains a record with a numeric suffix >= 4: snapshots produced by a different but-core version with another conflict encoding, hand-crafted or partially written snapshot objects, or an index corrupted by a crash mid-snapshot.","commonSituations":"Snapshot trees written by older/newer but-core code; repository object database partially corrupted; external tooling writing objects into the snapshot refs; restored backups with damaged trees.","solutions":["Run `git status` and `git fsck` on the affected repo; re-create the conflicted state if the index is damaged","Discard the bad snapshot and take a fresh one so the index-conflicts entries are rewritten by the current code","Verify no external tool wrote to .git or the snapshot refs","If it reproduces from a snapshot created by the same version, report a but-core bug with the snapshot tree id"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"let mut recorder = gix::traverse::tree::Recorder::default();\nconflict_tree.traverse().depthfirst(&mut recorder)?;\nlet valid = recorder.records.iter()\n    .filter(|r| !r.mode.is_tree())\n    .all(|r| {\n        r.filepath.rfind('/').is_some_and(|p| {\n            matches!(r.filepath[p + 1..].to_str(), Some(\"1\" | \"2\" | \"3\"))\n        })\n    });\nif !valid { anyhow::bail!(\"snapshot conflict records carry invalid stages\"); }","typeGuard":"fn is_conflict_stage_suffix(suffix: &str) -> bool {\n    matches!(suffix, \"1\" | \"2\" | \"3\")\n}","tryCatchPattern":"match resolve_tree(snapshot_tree, target, opts) {\n    Err(e) if e.to_string().starts_with(\"Invalid stage\") => {\n        // unrecoverable snapshot corruption: re-take the snapshot, do not retry\n        re_snapshot_and_retry()?;\n    }\n    other => other?,\n}","preventionTips":["Create and resolve snapshots with the same but-core version","Never hand-edit snapshot objects or .git/index","Keep external processes from writing to the repo during snapshot operations"],"tags":["git-index","conflict","snapshot","corruption","but-core"],"backgroundTag":"git-index-corruption","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}