{"record":{"id":"d6d7e8e1b7c6d253","repo":"gitbutlerapp/gitbutler","slug":"bug-id-is-queued","errorCode":null,"errorMessage":"BUG: {id} is queued","messagePattern":"BUG: (.+?) is queued","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-graph/src/init/types.rs","lineNumber":279,"sourceCode":"\n    fn record_hard_limit_if_exhausted(&mut self) -> bool {\n        let hard_limit_exhausted = self.is_hard_limit_exhausted();\n        self.hard_limit_hit |= hard_limit_exhausted;\n        hard_limit_exhausted\n    }\n\n    /// Stop accepting new items while leaving already queued items to drain.\n    pub(crate) fn exhaust(&mut self) {\n        self.exhausted = true;\n    }\n\n    /// Add `goal` as additional goal to `id` or panic if `id` was not found.\n    pub fn add_goal_to(&mut self, id: gix::ObjectId, goal: CommitFlags) {\n        let limit = self\n            .inner\n            .iter_mut()\n            .find_map(|(info, _, _, limit)| (info.id == id).then_some(limit))\n            .unwrap_or_else(|| panic!(\"BUG: {id} is queued\"));\n        *limit = limit.additional_goal(goal);\n    }\n}\n\n/// Various other - good to know what we need though.\nimpl Queue {\n    pub fn pop_front(&mut self) -> Option<QueueItem> {\n        self.inner.pop_front()\n    }\n    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut QueueItem> {\n        self.inner.iter_mut()\n    }\n    pub fn iter(&self) -> impl Iterator<Item = &QueueItem> {\n        self.inner.iter()\n    }\n}\n/// A set of commits to keep track of in bitflags.\n#[derive(Default)]","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-graph/src/init/types.rs#L261-L297","documentation":"`Queue::add_goal_to` (but-graph/src/init/types.rs:274) scans queued items for one whose `info.id` equals the given `gix::ObjectId` and adds `goal` to that item's limit. Despite the message text, the panic fires when the id is NOT found — `unwrap_or_else` on a `find_map` that matched nothing. So `BUG: {id} is queued` really means 'this commit was expected to be queued but is not in the queue anymore (or never was)'.","triggerScenarios":"Calling `add_goal_to` with a commit id that was never pushed into the `Queue`; the item was already `pop_front()`-ed or the queue was rebuilt; passing an id derived from a different graph snapshot than the queue being mutated.","commonSituations":"Graph initialization code that computes target commit ids before enqueueing them; concurrent-ish flows where the queue drains between computing ids and adding goals; refactors that reorder enqueue/goal steps.","solutions":["Ensure the commit for `id` is pushed into the same `Queue` instance before `add_goal_to` is called.","Do not reuse ids from a previous queue build; re-derive ids from the queue contents you are mutating.","Check membership via `Queue::iter_mut()` before calling, and enqueue or skip when absent."],"exampleFix":"// before\nqueue.add_goal_to(commit_id, goal); // panics if id not queued\n\n// after\nif queue.iter_mut().any(|(info, _, _, _)| info.id == commit_id) {\n    queue.add_goal_to(commit_id, goal);\n} else {\n    // enqueue the commit first, or skip with a diagnostic\n}","handlingStrategy":"validation","validationCode":"// verify the commit is still queued before adding a goal\nlet queued = queue\n    .iter_mut()\n    .any(|(info, _, _, _)| info.id == commit_id);\nif !queued {\n    // re-enqueue the commit, or skip goal assignment with a diagnostic\n    queue.push_front_exhausted(item_for(commit_id));\n}\nqueue.add_goal_to(commit_id, goal);","typeGuard":"fn is_queued(queue: &but_graph::init::types::Queue, id: gix::ObjectId) -> bool {\n    queue.iter_mut().any(|(info, _, _, _)| info.id == id)\n}","tryCatchPattern":"let ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe({\n    let queue = &mut queue;\n    move || queue.add_goal_to(id, goal)\n}));\nif ok.is_err() {\n    // id was not in the queue: rebuild ids from the current queue and retry once\n}","preventionTips":["Derive goal target ids from the same Queue instance that will process them, never from a stale snapshot.","Keep enqueue and add_goal_to adjacent in the code so draining between them is impossible.","Remember the message text is inverted: '{id} is queued' actually means the id was NOT found."],"tags":["rust","but-graph","internal-invariant","queue","panic","commit-ids"],"backgroundTag":"internal-invariant-panic","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}