{"record":{"id":"55b28825309f0371","repo":"astrid-runtime/astrid","slug":"mount-callback-response-is-too-large","errorCode":null,"errorMessage":"mount callback response is too large","messagePattern":"mount callback response is too large","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/storage_mount.rs","lineNumber":687,"sourceCode":"\n#[cfg(any(unix, windows))]\nasync fn write_response(\n    stream: &mut LocalStream,\n    response: CallbackResponse,\n) -> Result<(), io::Error> {\n    let bytes = match response {\n        CallbackResponse::V1(response) => serde_json::to_vec(&response),\n        CallbackResponse::V2(response) => serde_json::to_vec(&response),\n    }\n    .map_err(io::Error::other)?;\n    if bytes.len() > MAX_CALLBACK_FRAME_BYTES {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            \"mount callback response exceeds limit\",\n        ));\n    }\n    let length = u32::try_from(bytes.len()).map_err(|_| {\n        io::Error::new(\n            io::ErrorKind::InvalidData,\n            \"mount callback response is too large\",\n        )\n    })?;\n    stream.write_all(&length.to_be_bytes()).await?;\n    stream.write_all(&bytes).await?;\n    stream.flush().await\n}\n\nasync fn dispatch_request(\n    kernel: &Kernel,\n    state: &StorageMountLeaseState,\n    callback: CallbackRequest,\n) -> CallbackResponse {\n    let request = callback.request;\n    let request_id = request.request_id.clone();\n    let outcome = if !state.is_live() {\n        failure(\"stale-lease\", \"storage mount lease is expired or revoked\")","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/storage_mount.rs#L669-L705","documentation":"After the MAX_CALLBACK_FRAME_BYTES check, write_response converts the serialized length to u32 for the big-endian length prefix. If the length does not fit in u32 (practically: > 4 GiB, i.e. the frame-bytes check did not catch it), this second InvalidData error fires. It is a defensive guard ensuring the wire format's u32 length field is always representable.","triggerScenarios":"bytes.len() > u32::MAX after passing the MAX_CALLBACK_FRAME_BYTES check — only reachable if MAX_CALLBACK_FRAME_BYTES is configured above 4 GiB or the check is bypassed by code changes.","commonSituations":"Someone raised MAX_CALLBACK_FRAME_BYTES to u64/usize max to 'remove limits'; a refactor removed the earlier size check so giant responses reach the u32 conversion.","solutions":["Restore/keep MAX_CALLBACK_FRAME_BYTES below u32::MAX so the length always fits","Fix the earlier guard rather than the conversion: the frame-bytes check should reject the response first","If huge responses are required, redesign the protocol to chunk responses instead of enlarging the length prefix"],"exampleFix":"// before\nconst MAX_CALLBACK_FRAME_BYTES: usize = usize::MAX;\n// after\nconst MAX_CALLBACK_FRAME_BYTES: usize = u32::MAX as usize;","handlingStrategy":"try-catch","validationCode":"fn fits_u32_length_prefix(bytes_len: usize) -> bool {\n    bytes_len <= u32::MAX as usize\n}\nassert!(fits_u32_length_prefix(ser_len), \"length must fit u32 frame prefix\");","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string() == \"mount callback response is too large\" => {\n        eprintln!(\"protocol bug: response exceeded u32 length prefix; enforce frame cap\");\n    }\n    other => other?,\n}","preventionTips":["Never raise MAX_CALLBACK_FRAME_BYTES above u32::MAX","Keep the frame-size guard ahead of the u32 conversion in code review","Treat this error as an internal invariant breach, not a client-fixable condition"],"tags":["limit","framing","response"],"backgroundTag":"payload-too-large","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"}