{"record":{"id":"308d2aab34f522bf","repo":"Hmbown/CodeWhale","slug":"legacy-spillover-ownership-requires-a-session-id","errorCode":null,"errorMessage":"legacy spillover ownership requires a session id","messagePattern":"legacy spillover ownership requires a session id","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/truncate.rs","lineNumber":140,"sourceCode":"}\n\n#[must_use]\npub(crate) fn legacy_spillover_ownership_path(payload_path: &Path) -> PathBuf {\n    payload_path.with_extension(\"owner.json\")\n}\n\n/// Publish the proof needed to retrieve a legacy-global spillover safely.\n///\n/// Payload publication happens first. If this atomic sidecar write fails, the\n/// payload is deliberately left unowned and therefore inaccessible through\n/// `retrieve_tool_result`; callers must not advertise a retrieval hint.\npub(crate) fn publish_legacy_spillover_ownership(\n    payload_path: &Path,\n    session_id: &str,\n    bytes: &[u8],\n) -> io::Result<PathBuf> {\n    if session_id.trim().is_empty() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"legacy spillover ownership requires a session id\",\n        ));\n    }\n    let ownership = LegacySpilloverOwnership {\n        schema_version: LEGACY_SPILLOVER_OWNER_SCHEMA_VERSION,\n        origin_session: session_id.to_string(),\n        digest: crate::hashing::sha256_hex(bytes),\n        size_bytes: bytes.len().try_into().unwrap_or(u64::MAX),\n    };\n    let sidecar = legacy_spillover_ownership_path(payload_path);\n    let encoded = serde_json::to_vec_pretty(&ownership)\n        .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;\n    crate::utils::write_atomic(&sidecar, &encoded)?;\n    Ok(sidecar)\n}\n\npub(crate) fn read_legacy_spillover_ownership(","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/truncate.rs#L122-L158","documentation":"InvalidInput returned by publish_legacy_spillover_ownership when the session id passed in is empty or whitespace-only. The function writes the ownership sidecar that makes a legacy-global spillover payload retrievable; without a real session id the ownership record would be meaningless, so publication is refused before any bytes are written (the payload is deliberately left unowned and callers must not advertise a retrieval hint).","triggerScenarios":"Large tool output routed to the legacy-global spillover path before a session context exists — e.g. a headless/embedded run or early startup call where the caller passes \"\" or a blank session id into publish_legacy_spillover_ownership.","commonSituations":"New integration that streams tool output before creating/opening a session; refactoring that reordered session initialization; tests that exercise the truncate path without a session fixture; whitespace sneaking in through config-provided session labels.","solutions":["Establish (or load) the session first and pass its non-empty id before large output can spill over","Guard at the call site: if session_id.trim().is_empty(), keep output inline instead of publishing spillover","Audit callers added during refactors — this contract is easy to miss because the signature takes &str"],"exampleFix":"// before\npublish_legacy_spillover_ownership(&path, \"\", &bytes)?; // InvalidInput\n\n// after\nif let Some(sid) = session_id.filter(|s| !s.trim().is_empty()) {\n    publish_legacy_spillover_ownership(&path, sid, &bytes)?;\n} else {\n    // keep output inline; do not advertise a retrieval hint\n}","handlingStrategy":"validation","validationCode":"fn session_id_usable(id: &str) -> bool { !id.trim().is_empty() }\n\nif !session_id_usable(&session_id) {\n    // keep output inline; do not call publish_legacy_spillover_ownership\n}","typeGuard":"fn is_missing_session_id(e: &std::io::Error) -> bool {\n    e.kind() == std::io::ErrorKind::InvalidInput\n        && e.to_string().contains(\"requires a session id\")\n}","tryCatchPattern":null,"preventionTips":["Create or load a session before any code path that can spill large tool output","Type the contract into the call: pass SessionId, not String, where possible","Add a test exercising the truncate path without a session fixture to catch regressions"],"tags":["spillover","truncation","session-id","input-validation","rust"],"backgroundTag":"missing-session-id","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}