{"record":{"id":"ec506f6fa0896b53","repo":"atuinsh/atuin","slug":"daemon-sent-a-lag-notice-as-a-history-event","errorCode":null,"errorMessage":"daemon sent a lag notice as a history event","messagePattern":"daemon sent a lag notice as a history event","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin/src/command/client/history.rs","lineNumber":702,"sourceCode":"    #[serde(skip_serializing_if = \"Option::is_none\")]\n    duration_ns: Option<i64>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    duration: Option<String>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    success: Option<bool>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    finished_at: Option<String>,\n}\n\n#[cfg(feature = \"daemon\")]\nimpl TailEvent {\n    fn from_proto(reply: TailHistoryReply) -> Result<Self> {\n        let (kind, history) = match reply.event {\n            Some(TailEventProto::Started(history)) => (TailKind::Started, history),\n            Some(TailEventProto::Ended(history)) => (TailKind::Ended, history),\n            Some(TailEventProto::Cancelled(history)) => (TailKind::Cancelled, history),\n            Some(TailEventProto::Lagged(_)) => {\n                bail!(\"daemon sent a lag notice as a history event\")\n            }\n            None => bail!(\"daemon sent an unspecified history tail event\"),\n        };\n        let timestamp = OffsetDateTime::from_unix_nanos_i64(history.timestamp);\n        let author_kind = history.author_kind();\n\n        Ok(Self {\n            kind,\n            history: History {\n                id: history\n                    .id\n                    .ok_or_else(|| eyre::eyre!(\"daemon tail event is missing the history id\"))?\n                    .try_into()?,\n                timestamp,\n                duration: history.duration,\n                exit: history.exit,\n                command: history.command,\n                cwd: history.cwd,","sourceCodeStart":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/atuinsh/atuin/blob/c0c717ab04c881764bcad4b3d169a507e2432643/crates/atuin/src/command/client/history.rs#L684-L720","documentation":"TailHistoryReply::from_proto converts gRPC tail events into typed history events. A Lagged event is a valid tail-stream signal but must be handled as its own kind, not as a history event; if the daemon (or a desynced client) delivers it through the history-event path, this invariant is violated and the conversion bails.","triggerScenarios":"A daemon sends TailEventProto::Lagged through the history-event decode path in the tail stream, i.e. a malformed or out-of-contract stream message.","commonSituations":"CLI/daemon protocol skew where the daemon packs lag notices differently; bugs in a newer/older daemon; custom or patched daemon builds.","solutions":["Restart the daemon so client and server protocol versions match","Upgrade both CLI and daemon to the same atuin version","Report as a bug if it persists with matched versions"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match TailHistoryReply::from_proto(reply) {\n    Err(e) if e.to_string().contains(\"lag notice as a history event\") => {\n        // handle Lagged events on the lag path instead; restart daemon to fix skew\n    }\n    other => other?,\n}","preventionTips":["Run matching CLI/daemon versions","Handle Lagged as its own event kind in stream consumers","Keep the tail-stream decode paths synchronized with the proto contract"],"tags":["daemon","grpc","protocol","streaming"],"backgroundTag":"unexpected-response-shape","analyzedSha":"c0c717ab04c881764bcad4b3d169a507e2432643","analyzedAt":"2026-09-12T07:40:01.341Z","contentChangedAt":"2026-09-12T07:40:01.341Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}