{"record":{"id":"7d4281a97f83d3d3","repo":"astrid-runtime/astrid","slug":"invaliddata-7d4281","errorCode":"InvalidData","errorMessage":"session token hex must be 64 chars, got {}","messagePattern":"session token hex must be 64 chars, got (.+?)","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/session_token.rs","lineNumber":83,"sourceCode":"    #[must_use]\n    pub fn to_hex(&self) -> String {\n        let mut hex = String::with_capacity(64);\n        for byte in &self.0 {\n            use fmt::Write;\n            let _ = write!(hex, \"{byte:02x}\");\n        }\n        hex\n    }\n\n    /// Decode a hex-encoded token string.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the hex string is not exactly 64 characters or\n    /// contains invalid hex digits.\n    pub fn from_hex(hex: &str) -> Result<Self, io::Error> {\n        if hex.len() != 64 {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\"session token hex must be 64 chars, got {}\", hex.len()),\n            ));\n        }\n        let mut bytes = [0u8; 32];\n        for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {\n            let hi = hex_digit(chunk[0])?;\n            let lo = hex_digit(chunk[1])?;\n            bytes[i] = (hi << 4) | lo;\n        }\n        Ok(Self(bytes))\n    }\n\n    /// Write the token to a file with owner-only permissions (0o600).\n    ///\n    /// On Unix, this uses write-then-rename atomicity: writes to a temporary\n    /// file at 0o600 (via `OpenOptions::mode` to avoid a TOCTOU permissions\n    /// window), then atomically renames it to the target path. This prevents","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/session_token.rs#L65-L101","documentation":"SessionToken::from_hex parses a hex-encoded 32-byte session token and requires the input string to be exactly 64 hex characters. A string of any other length is rejected up front with InvalidData before any decoding is attempted. This guarantees the token always decodes to exactly [u8; 32].","triggerScenarios":"Calling SessionToken::from_hex with a string whose byte length is not 64 — e.g. a truncated token, a token with extra whitespace, a base64-encoded token, or a raw 32-byte value pasted as text.","commonSituations":"Copy/paste truncating or padding the token; storing tokens in a fixed-width DB column that truncated them; accidentally encoding the token as base64 instead of hex; including a '0x' prefix which adds 2 characters.","solutions":["Ensure the input is exactly 64 lowercase/uppercase hex characters (32 bytes) before calling from_hex.","Trim whitespace and remove any '0x' prefix from the token string.","Regenerate the session token if the value was truncated in storage or transit.","If you have raw bytes, construct the token directly rather than round-tripping through a lossy string."],"exampleFix":"// before\nlet token = SessionToken::from_hex(&config.session_token)?; // \"a3f1...\" (60 chars)\n// after\nlet hex = config.session_token.trim();\nassert!(hex.len() == 64, \"token must be 64 hex chars\");\nlet token = SessionToken::from_hex(hex)?;","handlingStrategy":"validation","validationCode":"fn valid_token_hex(s: &str) -> bool {\n    s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Store tokens as hex strings and validate length at config-load time","Trim whitespace and strip '0x' prefixes before parsing","Use a fixed CHAR(64) column for tokens to catch truncation early"],"tags":["hex","validation","session-token","format"],"backgroundTag":"invalid-argument-format","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}