{"record":{"id":"5129e941261c9164","repo":"astrid-runtime/astrid","slug":"fskit-service-control-request-exceeds-limit","errorCode":null,"errorMessage":"FSKit service control request exceeds limit","messagePattern":"FSKit service control request exceeds limit","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-storage-provider-fskit/src/service.rs","lineNumber":317,"sourceCode":"            _ = poll.tick() => {\n                if !parent_is_alive(&launch.parent) || probe_callback(launch).await.is_err() {\n                    return Ok(());\n                }\n            },\n        }\n    }\n}\n\nasync fn read_control(stream: &mut LocalStream) -> Result<ControlRequest> {\n    let mut line = String::new();\n    let reader = tokio::io::BufReader::new(stream);\n    let read = reader\n        .take((MAX_CONTROL_BYTES + 1) as u64)\n        .read_line(&mut line)\n        .await\n        .context(\"read FSKit service control request\")?;\n    if read == 0 || line.len() > MAX_CONTROL_BYTES {\n        bail!(\"FSKit service control request exceeds limit\");\n    }\n    serde_json::from_str(&line).context(\"decode FSKit service control request\")\n}\n\nasync fn write_control(stream: &mut LocalStream, response: &ControlResponse) -> Result<()> {\n    let bytes = serde_json::to_vec(response)?;\n    stream.write_all(&bytes).await?;\n    stream.write_all(b\"\\n\").await?;\n    stream.flush().await.context(\"flush FSKit service control\")\n}\n\n#[cfg(unix)]\nfn parent_is_alive(\n    parent: &astrid_core::storage_filesystem::StorageProviderParentLifetimeV1,\n) -> bool {\n    use nix::sys::signal::kill;\n    use nix::unistd::Pid;\n","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage-provider-fskit/src/service.rs#L299-L335","documentation":"read_control reads one newline-delimited JSON control request from the service socket using a take(MAX_CONTROL_BYTES + 1) cap. If the line is empty (EOF) or longer than MAX_CONTROL_BYTES, it is rejected. This bounds memory per control message and treats EOF-with-no-data as a protocol error.","triggerScenarios":"read_control receives either 0 bytes before newline (client disconnected without sending) or a line exceeding MAX_CONTROL_BYTES — e.g. a client sending a huge un-terminated line or binary garbage on the control socket.","commonSituations":"A client writing a control request without a trailing newline then being truncated; an over-large serialized request; something other than the intended client connected to the socket and wrote junk.","solutions":["Ensure control clients send newline-terminated JSON within MAX_CONTROL_BYTES","Split oversized control requests or extend MAX_CONTROL_BYTES deliberately if legitimate payloads grew","Verify no stray process is writing to the control socket path","Check the client didn't crash mid-write (EOF case) and reconnect"],"exampleFix":"// client side\n// before\nstream.write_all(payload)?;\n// after\nstream.write_all(payload)?;\nstream.write_all(b\"\\n\")?;","handlingStrategy":"validation","validationCode":"fn control_payload_ok(json: &str, max: usize) -> bool { json.len() <= max && json.ends_with('\\n') }","typeGuard":null,"tryCatchPattern":"match read_control(&mut reader).await { Err(e) if e.to_string().contains(\"exceeds limit\") => { drop_client(); continue; } other => other }","preventionTips":["Always terminate control requests with a newline","Keep control messages small and split large payloads","Check for unexpected writers on the control socket","Handle client disconnects (EOF) as a normal reconnect case"],"tags":["ipc","limits","framing"],"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-14T05:17:10.506Z"}