atuinsh/atuin · error
daemon sent a lag notice as a history event
Error message
daemon sent a lag notice as a history event
What it means
`TailHistoryReply::from_proto` converts daemon tail-stream events into client-side events, but only Started/Ended/Cancelled carry history payloads. A `Lagged` event arriving where a history event was expected is a protocol violation by the daemon, so the client errors out.
Solutions
- Restart the daemon so client and daemon use matching versions/protocol.
- Update the atuin binary (and daemon) to the same version.
- Handle the Lagged event in the tail loop instead of treating every event as a history event (report upstream if reproducible).
Defensive patterns
Strategy: type-guard
Type guard
fn is_history_event(reply: &TailHistoryReply) -> bool {
matches!(
reply.event,
Some(TailEventProto::Started(_))
| Some(TailEventProto::Ended(_))
| Some(TailEventProto::Cancelled(_))
)
} Try / catch
match TailHistoryReply::from_proto(reply) {
Ok(ev) => handle(ev),
Err(e) if e.to_string().contains("lag notice") => handle_lag(),
Err(e) => return Err(e),
} Prevention
- Run daemon and client from the same atuin version.
- Treat Lagged as its own stream signal, not a history event.
- Report persistent protocol violations upstream.
When it happens
Trigger: `atuin history tail` (or the tail streaming API) when the daemon sends a `Lagged` variant inside an event slot that from_proto expects to be a history event.
Common situations: Daemon/client version skew where lag signaling changed; a daemon bug or misrouted gRPC event; missed back-pressure causing lag notices to be misdelivered.
Related errors
- daemon sent an unspecified history tail event
- {message}
- `atuin history tail` requires Atuin to be built with the…
- could not sync records due to version mismatch
- daemon already running
AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19).
Data as JSON: /api/errors/ec506f6fa0896b53.
Report an issue: GitHub.
Appendix: source
Thrown at crates/atuin/src/command/client/history.rs:700
#[serde(skip_serializing_if = "Option::is_none")]
duration_ns: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
duration: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
success: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
finished_at: Option<String>,
}
#[cfg(feature = "daemon")]
impl TailEvent {
fn from_proto(reply: TailHistoryReply) -> Result<Self> {
let (kind, history) = match reply.event {
Some(TailEventProto::Started(history)) => (TailKind::Started, history),
Some(TailEventProto::Ended(history)) => (TailKind::Ended, history),
Some(TailEventProto::Cancelled(history)) => (TailKind::Cancelled, history),
Some(TailEventProto::Lagged(_)) => {
bail!("daemon sent a lag notice as a history event")
}
None => bail!("daemon sent an unspecified history tail event"),
};
let timestamp = OffsetDateTime::from_unix_nanos_i64(history.timestamp);
let author_kind = history.author_kind();
Ok(Self {
kind,
history: History {
id: history
.id
.ok_or_else(|| eyre::eyre!("daemon tail event is missing the history id"))?
.try_into()?,
timestamp,
duration: history.duration,
exit: history.exit,
command: history.command,
cwd: history.cwd,View on GitHub (pinned to 900007c480)