Hmbown/CodeWhale · error
lifecycle outbox writer task is gone
Error message
lifecycle outbox writer task is gone
What it means
enqueue's bounded channel send returned TrySendError::Closed, meaning the spawned lifecycle outbox writer task no longer exists (it panicked, was aborted, or the receiver was dropped). The library surfaces this instead of silently losing events.
Solutions
- Restart the outbox/writer (ensure_writer_spawned re-arms it; recreate the outbox instance if it stays closed)
- Find and fix the panic in the writer task's flush loop from the task's logs
- Avoid emitting events after runtime shutdown; gate emitters on shutdown signals
Defensive patterns
Strategy: fallback
Validate before calling
if outbox.writer_closed() {
eprintln!("outbox writer gone; re-spawning before emitting");
outbox.ensure_writer_spawned();
} Try / catch
match outbox.enqueue(event) {
Err(e) if e.to_string().contains("writer task is gone") => {
outbox.ensure_writer_spawned();
outbox.enqueue(event).or_else(|_| { dead_letter_log(event); Ok(()) })?;
Ok(())
}
other => other,
} Prevention
- Wrap the writer task's body so panics are caught and logged, then respawn
- Gate event emitters on runtime shutdown state
- Alert on writer task exit in long-running sessions
When it happens
Trigger: Calling enqueue after the writer task died — e.g. runtime shutdown, a panic in the writer's flush loop, or explicit abort of the task.
Common situations: Application shutting down while events are still emitted; a bug or I/O failure panicking the writer task; runtime teardown ordering issues.
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
- Compaction lifecycle task ended before acknowledgement
- lifecycle outbox queue is full; flush rejected
- Turn lifecycle task ended before acknowledgement
- background hook supervisor queue is full
- ChatGPT revoke task was lost
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/09e97e3400a15084.
Report an issue: GitHub.
Appendix: source
Thrown at crates/hooks/src/lifecycle_outbox.rs:245
/// 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) {
return;
}
let Ok(handle) = tokio::runtime::Handle::try_current() else {View on GitHub (pinned to 73e0f67d83)