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

  1. Ensure the commit for `id` is pushed into the same `Queue` instance before `add_goal_to` is called.
  2. Do not reuse ids from a previous queue build; re-derive ids from the queue contents you are mutating.
  3. 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

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


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/d6d7e8e1b7c6d253. Report an issue: GitHub.