gitbutlerapp/gitbutler · error
BUG: {id} is queued
Error message
BUG: {id} is queued What it means
`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)'.
Source
Thrown at crates/but-graph/src/init/types.rs:279
fn record_hard_limit_if_exhausted(&mut self) -> bool {
let hard_limit_exhausted = self.is_hard_limit_exhausted();
self.hard_limit_hit |= hard_limit_exhausted;
hard_limit_exhausted
}
/// Stop accepting new items while leaving already queued items to drain.
pub(crate) fn exhaust(&mut self) {
self.exhausted = true;
}
/// Add `goal` as additional goal to `id` or panic if `id` was not found.
pub fn add_goal_to(&mut self, id: gix::ObjectId, goal: CommitFlags) {
let limit = self
.inner
.iter_mut()
.find_map(|(info, _, _, limit)| (info.id == id).then_some(limit))
.unwrap_or_else(|| panic!("BUG: {id} is queued"));
*limit = limit.additional_goal(goal);
}
}
/// Various other - good to know what we need though.
impl Queue {
pub fn pop_front(&mut self) -> Option<QueueItem> {
self.inner.pop_front()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut QueueItem> {
self.inner.iter_mut()
}
pub fn iter(&self) -> impl Iterator<Item = &QueueItem> {
self.inner.iter()
}
}
/// A set of commits to keep track of in bitflags.
#[derive(Default)]View on GitHub (pinned to caf1f223d3)
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.
Example fix
// before
queue.add_goal_to(commit_id, goal); // panics if id not queued
// after
if queue.iter_mut().any(|(info, _, _, _)| info.id == commit_id) {
queue.add_goal_to(commit_id, goal);
} else {
// enqueue the commit first, or skip with a diagnostic
} Defensive patterns
Strategy: validation
Validate before calling
// verify the commit is still queued before adding a goal
let queued = queue
.iter_mut()
.any(|(info, _, _, _)| info.id == commit_id);
if !queued {
// re-enqueue the commit, or skip goal assignment with a diagnostic
queue.push_front_exhausted(item_for(commit_id));
}
queue.add_goal_to(commit_id, goal); Type guard
fn is_queued(queue: &but_graph::init::types::Queue, id: gix::ObjectId) -> bool {
queue.iter_mut().any(|(info, _, _, _)| info.id == id)
} Try / catch
let ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe({
let queue = &mut queue;
move || queue.add_goal_to(id, goal)
}));
if ok.is_err() {
// id was not in the queue: rebuild ids from the current queue and retry once
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- handled above
- prefiltered
- swap connections of nodes as well
- rebase is always Some(_)
- broker already configured
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/d6d7e8e1b7c6d253.
Report an issue: GitHub.