{"record":{"id":"d12b2b580d092352","repo":"Hmbown/CodeWhale","slug":"invalid-runtime-chat-virtual-turn-id","errorCode":null,"errorMessage":"invalid Runtime Chat virtual turn id","messagePattern":"invalid Runtime Chat virtual turn id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_chat_relay.rs","lineNumber":1729,"sourceCode":"    if value.len() != 37\n        || !value.starts_with(\"local_thread_\")\n        || !value[13..]\n            .bytes()\n            .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))\n    {\n        bail!(\"invalid Runtime Chat virtual thread id\");\n    }\n    Ok(())\n}\n\nfn validate_virtual_turn_id(value: &str) -> Result<()> {\n    if value.len() != 35\n        || !value.starts_with(\"local_turn_\")\n        || !value[11..]\n            .bytes()\n            .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))\n    {\n        bail!(\"invalid Runtime Chat virtual turn id\");\n    }\n    Ok(())\n}\n\nfn validate_native_record_id(value: &str, label: &str) -> Result<()> {\n    if value.len() < 5\n        || value.len() > 80\n        || !value\n            .bytes()\n            .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')\n    {\n        bail!(\"invalid {label}\");\n    }\n    Ok(())\n}\n\nfn validate_route_id(value: &str, label: &str) -> Result<()> {\n    if !crate::runtime_api::runtime_chat_route_id_is_safe(value) {","sourceCodeStart":1711,"sourceCodeEnd":1747,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_chat_relay.rs#L1711-L1747","documentation":"validate_virtual_turn_id requires virtual turn ids to be exactly 35 bytes, start with \"local_turn_\", and have a suffix of only ASCII digits and lowercase hex letters. Like virtual thread ids, these must come from the local generator; any other shape is rejected.","triggerScenarios":"Calling validate_virtual_turn_id (directly or via RelayState::validate over binding turns) with an id that is not 35 chars, lacks the \"local_turn_\" prefix, or whose 23-char suffix contains uppercase, non-hex letters, or punctuation.","commonSituations":"Hand-built turn ids in tests/tooling with wrong length; using a message or provider turn id where a locally generated id is required; string truncation or accidental whitespace.","solutions":["Generate the id with the local virtual-turn-id generator","Fix the prefix to \"local_turn_\" and make the suffix exactly 23 hex/digit chars","Downcase hex and strip dashes/punctuation from the suffix","Verify length: 11 (prefix) + 23 (suffix) = 35 characters"],"exampleFix":"// before\nlet id = \"local_turn_42\"; // too short\n// after\nlet id = \"local_turn_0123456789abcdef0123456\"; // 11 + 23 = 35 chars","handlingStrategy":"validation","validationCode":"fn looks_like_virtual_turn_id(value: &str) -> bool {\n    value.len() == 35\n        && value.starts_with(\"local_turn_\")\n        && value[11..].bytes().all(|b| b.is_ascii_digit() || matches!(b, b'a'..=b'f'))\n}","typeGuard":"fn as_virtual_turn_id(value: &str) -> Option<&str> {\n    looks_like_virtual_turn_id(value).then_some(value)\n}","tryCatchPattern":"if !looks_like_virtual_turn_id(&turn_id) {\n    eprintln!(\"regenerating virtual turn id (got {turn_id:?})\");\n    let turn_id = generate_virtual_turn_id();\n}","preventionTips":["Only create virtual turn ids with the local generator","Keep the 11+23=35 length invariant in a constructor function","Do not substitute provider message ids for virtual turn ids"],"tags":["validation","identifier","format"],"backgroundTag":"invalid-identifier-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}