{"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":702,"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":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/atuinsh/atuin/blob/900007c48045867e7f44b0f9bc55ad9dbe99ee0d/crates/atuin/src/command/client/history.rs#L684-L720","documentation":"`TailHistoryReply::from_proto` requires every event to carry a concrete `TailEventProto` variant. When the gRPC oneof is `None` (unspecified/absent), there is no history event to decode, so the client bails. This indicates a malformed or empty event from the daemon.","triggerScenarios":"Streaming `atuin history tail` and receiving a `TailHistoryReply` whose `event` field is unset (None).","commonSituations":"Daemon/client version skew (new daemon field the old client can't decode, collapsing the oneof to None); proxy truncating messages; daemon bug emitting empty replies.","solutions":["Update client and daemon to matching versions and restart the daemon.","Retry the tail command; if persistent, report a bug with daemon logs.","Check for intermediaries (proxies) that might corrupt the gRPC stream."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn has_event(reply: &TailHistoryReply) -> bool {\n    reply.event.is_some()\n}","tryCatchPattern":"if reply.event.is_none() {\n    eprintln!(\"skipping empty tail event (version skew?)\");\n    continue;\n}\nlet ev = TailHistoryReply::from_proto(reply)?;","preventionTips":["Keep daemon and client versions identical.","Restart the daemon after upgrades.","Retry the stream once on empty events before failing."],"tags":["daemon","grpc","protocol","history-tail"],"backgroundTag":"unexpected-response-shape","analyzedSha":"900007c48045867e7f44b0f9bc55ad9dbe99ee0d","analyzedAt":"2026-09-19T12:52:51.491Z","contentChangedAt":"2026-09-19T12:52:51.491Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}