{"record":{"id":"5200bccda263bbd8","repo":"gitbutlerapp/gitbutler","slug":"invalid-parent-delimitation-requested-parent-is-n","errorCode":null,"errorMessage":"Invalid parent delimitation: requested parent is not a direct parent of target.parent","messagePattern":"Invalid parent delimitation: requested parent is not a direct parent of target\\.parent","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-rebase/src/graph_rebase/mutate.rs","lineNumber":454,"sourceCode":"            .edges_directed(target_parent.id, Direction::Outgoing)\n            .map(|e| (e.id(), e.weight().to_owned(), e.target()))\n            .collect::<Vec<_>>();\n\n        // All available parents\n        let available_parents = outgoing_edges\n            .iter()\n            .map(|(_, _, edge_target)| *edge_target)\n            .collect::<HashSet<_>>();\n        let available_children = incoming_edges\n            .iter()\n            .map(|(_, _, edge_source)| *edge_source)\n            .collect::<HashSet<_>>();\n\n        // 1. Verify that all parents and children to disconnect are directly connected to the target segment.\n        if let Some(parents_to_disconnect) = parents_to_disconnect.as_ref() {\n            for selector in parents_to_disconnect {\n                if !available_parents.contains(&selector.id) {\n                    return Err(anyhow!(\n                        \"Invalid parent delimitation: requested parent is not a direct parent of target.parent\"\n                    ));\n                }\n            }\n        }\n\n        if let Some(children_to_disconnect) = children_to_disconnect.as_ref() {\n            for selector in children_to_disconnect {\n                if !available_children.contains(&selector.id) {\n                    return Err(anyhow!(\n                        \"Invalid parent delimitation: requested child is not a direct parent of target.child\"\n                    ));\n                }\n            }\n        }\n\n        let parent_ids_to_disconnect = parents_to_disconnect\n            .as_ref()","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-rebase/src/graph_rebase/mutate.rs#L436-L472","documentation":"`Editor::disconnect_segment_from` validates that every selector in `parents_to_disconnect` is directly connected to `target.parent` (it must appear among the outgoing edges of `target.parent` in the segment graph). A selector that names an existing but non-adjacent commit fails this check — 'direct parent' means adjacent in the current graph topology, not just any ancestor.","triggerScenarios":"Passing a grandparent, sibling, or a commit from another stack as a parent to disconnect; selectors computed against an older graph snapshot whose edges have since changed; normalizing selectors against a different target than the one used for validation.","commonSituations":"Callers deriving disconnect sets from user multi-select (easy to include non-adjacent commits); graph mutated between selector computation and the disconnect call; porting graph surgery logic that assumed ancestry rather than adjacency.","solutions":["Build the parent set from the graph itself: enumerate outgoing edges of `target.parent` (what validation uses) and pick from those","Refresh the editor/graph and recompute selectors immediately before calling disconnect","If the intent is a deeper disconnect, perform multiple adjacent `disconnect_segment_from` calls instead of one non-adjacent one"],"exampleFix":"// before\nlet parents = SelectorSet::Some(SomeSelectors::new(user_selected_commits)?); // may include non-adjacent ids\neditor.disconnect_segment_from(target, children, parents, false)?; // Err\n\n// after — derive parents from the graph's actual edges\nlet adjacent: Vec<AnySelector> = graph\n    .edges_directed(target_parent_id, petgraph::Direction::Outgoing)\n    .map(|e| AnySelector::Commit(e.target().attach(repo)))\n    .collect();\nlet parents = SelectorSet::Some(SomeSelectors::new(adjacent)?);","handlingStrategy":"validation","validationCode":"// Restrict candidate parents to nodes directly connected to target.parent:\nlet adjacent: Vec<_> = graph\n    .edges_directed(target_parent_id, petgraph::Direction::Outgoing)\n    .map(|e| e.target())\n    .collect();\nanyhow::ensure!(\n    requested_parents.iter().all(|p| adjacent.contains(p)),\n    \"non-adjacent parent requested\"\n);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive disconnect sets from graph edges, never from user selection or ancestry walks","Compute selectors and perform the disconnect in one pass over a single graph snapshot","Snapshot-test graph surgery operations so topology assumptions are pinned"],"tags":["rust","graph-rebase","git","topology","validation"],"backgroundTag":"graph-topology-violation","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}