{"record":{"id":"95a176b73c00d4c8","repo":"atuinsh/atuin","slug":"daemon-sent-an-unspecified-history-tail-event","errorCode":null,"errorMessage":"daemon sent an unspecified history tail event","messagePattern":"daemon sent an unspecified history tail event","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin/src/command/client/history.rs","lineNumber":704,"sourceCode":"    #[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,\n                session: history.session,\n                #[allow(deprecated)]","sourceCodeStart":686,"sourceCodeEnd":722,"githubUrl":"https://github.com/atuinsh/atuin/blob/c0c717ab04c881764bcad4b3d169a507e2432643/crates/atuin/src/command/client/history.rs#L686-L722","documentation":"from_proto requires every tail event to carry a concrete event kind (Started, Ended, Cancelled, or Lagged). A TailHistoryReply with a None event means the daemon sent a message with no event payload set, which is outside the wire contract, so conversion fails.","triggerScenarios":"Daemon sends a TailHistoryReply where reply.event is None (the oneof is unset) while the client decodes it as a history event.","commonSituations":"Protocol version skew or daemon bug producing empty oneof fields; interrupted/degraded stream frames from an old daemon.","solutions":["Restart the daemon to ensure matching protocol versions","Upgrade CLI and daemon to the same atuin release","File a bug if reproducible on 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(\"unspecified history tail event\") => {\n        // skip/drop the frame and reconnect the tail stream\n    }\n    other => other?,\n}","preventionTips":["Keep CLI and daemon on the same release","Treat None events as protocol violations and reconnect","Add daemon-side tests asserting every reply sets the event oneof"],"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"}