{"record":{"id":"cf5d8f17a8d1db0e","repo":"atuinsh/atuin","slug":"id-should-be-16-bytes","errorCode":null,"errorMessage":"id should be 16 bytes","messagePattern":"id should be 16 bytes","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin/src/command/client/search/engines/daemon.rs","lineNumber":199,"sourceCode":"\n        let mut ids = Vec::with_capacity(200);\n        span!(Level::TRACE, \"daemon_search.resp\")\n            .in_scope(async || -> Result<()> {\n                while let Some(response) = stream.message().await? {\n                    let span2 = span!(\n                        Level::TRACE,\n                        \"daemon_search.resp.item\",\n                        query_id = response.query_id\n                    );\n                    let span2_guard = span2.enter();\n                    // Only process if the query_id matches (prevents stale responses)\n                    if response.query_id == query_id {\n                        let uuids = response\n                            .ids\n                            .iter()\n                            .map(|id| {\n                                let bytes: [u8; 16] =\n                                    id.as_slice().try_into().expect(\"id should be 16 bytes\");\n                                Uuid::from_bytes(bytes).as_simple().to_string()\n                            })\n                            .collect::<Vec<_>>();\n                        ids.extend(uuids);\n                    }\n                    drop(span2_guard);\n                    drop(span2);\n                }\n                Ok(())\n            })\n            .await?;\n        drop(span);\n\n        if ids.is_empty() {\n            debug!(query = %query, results = 0, \"[daemon-client] empty results\");\n            return Ok(Vec::new());\n        }\n","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/atuinsh/atuin/blob/202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1/crates/atuin/src/command/client/search/engines/daemon.rs#L181-L217","documentation":"The daemon search engine streams results from the background atuin daemon over gRPC; each id is a 16-byte UUID in wire form. The client converts each id with id.as_slice().try_into::<[u8; 16]>() and expects success. A response carrying an id whose byte length differs (short, long, corrupt) violates the protocol and panics with 'id should be 16 bytes' inside the search flow.","triggerScenarios":"full_query() processing a SearchResponse whose ids contain anything other than exactly 16 bytes: a client/daemon version mismatch that changes the id encoding, a truncated or corrupted gRPC message, or a daemon bug emitting malformed ids.","commonSituations":"Upgrading the atuin CLI while the old daemon keeps running (client and daemon from different versions); a stale daemon socket after a partial upgrade; protocol drift between atuin components.","solutions":["Restart the daemon so client and daemon run the same atuin version (kill the stale process; it autostarts on next search)","Verify client and daemon come from the same install: compare atuin --version with the daemon's version/logs","If it persists, report it with daemon logs — a non-16-byte id is a protocol violation bug"],"exampleFix":"// before\nlet bytes: [u8; 16] = id.as_slice().try_into().expect(\"id should be 16 bytes\");\n\n// after — skip malformed ids instead of crashing the TUI\nlet Ok(bytes) = <[u8; 16]>::try_from(id.as_slice()) else {\n    tracing::warn!(len = id.len(), \"daemon sent a non-16-byte id; skipping\");\n    continue;\n};","handlingStrategy":"validation","validationCode":"// drop malformed ids before conversion\nlet uuids: Vec<_> = response\n    .ids\n    .iter()\n    .filter(|id| id.len() == 16)\n    .map(|id| Uuid::from_bytes(id.as_slice().try_into().unwrap()))\n    .collect();","typeGuard":"fn is_uuid_bytes(id: &[u8]) -> bool {\n    id.len() == 16\n}","tryCatchPattern":"// isolate per-item conversion so one bad id fails a single search, not the TUI\nlet uuids: Vec<_> = response.ids.iter().filter_map(|id| {\n    <[u8; 16]>::try_from(id.as_slice())\n        .ok()\n        .map(|b| Uuid::from_bytes(b).as_simple().to_string())\n}).collect();","preventionTips":["Restart the daemon after every atuin upgrade so both sides speak the same protocol","Pin client and daemon to the same version in deployments","Watch daemon logs for malformed SearchResponse messages after upgrades"],"tags":["uuid","grpc","daemon","protocol","panic","version-mismatch"],"backgroundTag":"uuid-byte-length-mismatch","analyzedSha":"202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1","analyzedAt":"2026-08-16T19:30:24.731Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}