Hmbown/CodeWhale · error

lifecycle outbox queue is full; flush rejected

Error message

lifecycle outbox queue is full; flush rejected

What it means

enqueue pushes lifecycle events onto a bounded mpsc outbox queue (capacity OUTBOX_QUEUE_CAPACITY) and flush is rejected when the queue is full. The library refuses to enqueue (and thus flush) rather than blocking the caller, signaling backpressure as an explicit error.

Solutions

  1. Slow down or batch event production so the writer can drain the queue
  2. Check why the writer task is not draining (stuck I/O, task panic) and restart the component
  3. Increase OUTBOX_QUEUE_CAPACITY if the workload legitimately needs more buffer
Defensive patterns

Strategy: fallback

Validate before calling

// check queue pressure if the outbox exposes it
if outbox.approx_pending() >= OUTBOX_QUEUE_CAPACITY - 1 {
    eprintln!("outbox near capacity; coalescing events");
}

Try / catch

if let Err(e) = outbox.enqueue(event).await {
    if e.to_string().contains("queue is full") {
        // drop the event (it is already logged) or persist locally and flush later
        dead_letter_log(event);
    }
}

Prevention

When it happens

Trigger: Calling enqueue while the outbox's bounded channel already holds OUTBOX_QUEUE_CAPACITY pending events — the writer task is slower than producers or is not draining.

Common situations: Bursts of lifecycle events overwhelming the writer; the writer task stalled on slow I/O or a blocked downstream; very high event volume in a long-running session.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/eebb0aa625701695. Report an issue: GitHub.

Appendix: source

Thrown at crates/hooks/src/lifecycle_outbox.rs:242

    ///
    /// Ordering: `send` happens before the spawn so events queued before the
    /// writer starts are drained first, preserving enqueue order. The queue
    /// is bounded: a wedged consumer drops further events with a warning
    /// (observability, not control flow) but fails a flush fast instead of
    /// dropping its reply channel.
    fn enqueue(self: &Arc<Self>, command: OutboxCommand) -> Result<()> {
        let is_flush = matches!(command, OutboxCommand::Flush(_));
        match self.sender.try_send(command) {
            Ok(()) => {}
            Err(TrySendError::Full(_)) if !is_flush => {
                tracing::warn!(
                    target: "lifecycle_outbox",
                    queue_capacity = OUTBOX_QUEUE_CAPACITY,
                    "lifecycle event dropped: outbox queue is full"
                );
            }
            Err(TrySendError::Full(_)) => {
                anyhow::bail!("lifecycle outbox queue is full; flush rejected");
            }
            Err(TrySendError::Closed(_)) => {
                anyhow::bail!("lifecycle outbox writer task is gone");
            }
        }
        self.ensure_writer_spawned();
        Ok(())
    }

    fn ensure_writer_spawned(self: &Arc<Self>) {
        if self.writer_spawned.load(Ordering::Acquire) {
            return;
        }
        let _guard = self
            .spawn_lock
            .lock()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        if self.writer_spawned.load(Ordering::Acquire) {

View on GitHub (pinned to 73e0f67d83)