{"record":{"id":"943b2ce78b72b420","repo":"Hmbown/CodeWhale","slug":"member-name-ended-before-its-recorded-size-of-expected-bytes","errorCode":null,"errorMessage":"member {name} ended before its recorded size of {expected} bytes; the source changed during export","messagePattern":"member (.+?) ended before its recorded size of (.+?) bytes; the source changed during export","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/session_export.rs","lineNumber":421,"sourceCode":"}\n\n/// Append exactly `expected` bytes of `reader` under `name`. The bounded read\n/// keeps the tar header and the member payload consistent when the source\n/// file changes size mid-export: growth is capped at the snapshot size\n/// (valid archive, prefix content), while shrinkage — which would otherwise\n/// silently shift every following header and corrupt the archive — fails the\n/// export instead.\nfn append_sized<W: Write, R: Read>(\n    tar: &mut Builder<W>,\n    header: &mut tar::Header,\n    name: &str,\n    reader: R,\n    expected: u64,\n) -> io::Result<()> {\n    let mut limited = reader.take(expected);\n    tar.append_data(header, name, &mut limited)?;\n    if limited.limit() != 0 {\n        return Err(io::Error::new(\n            io::ErrorKind::UnexpectedEof,\n            format!(\n                \"member {name} ended before its recorded size of {expected} bytes; the source changed during export\"\n            ),\n        ));\n    }\n    Ok(())\n}\n\nfn append_member<W: Write>(\n    tar: &mut Builder<W>,\n    name: &str,\n    contents: MemberContents<'_>,\n    members: &mut Vec<SessionArchiveMember>,\n) -> io::Result<()> {\n    match contents {\n        MemberContents::Bytes(bytes) => {\n            let mut header = archive_header(bytes.len() as u64);","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/session_export.rs#L403-L439","documentation":"append_sized writes an archive member using reader.take(expected) and then verifies the take() limit reached zero, i.e. the source yielded exactly the expected byte count recorded in the tar header. If the file shrank (or was truncated) between stat and read, the member is short and the export fails with UnexpectedEof rather than writing a corrupt archive.","triggerScenarios":"Calling append_sized (via append_member in write_session_archive) when the artifact file's size at header-build time is larger than the bytes actually readable — the file was truncated, deleted, or rewritten concurrently during export. Also exercised directly by the forkguard test feeding a short reader.","commonSituations":"Another process (a running agent, log rotator, editor save) modified or truncated the artifact while the tar was being written; exporting a live session whose logs are still being appended/rotated; NFS or network filesystems serving stale size metadata.","solutions":["Re-run the export now that the source has settled — a transient concurrent write is the usual cause.","Stop or quiesce the process writing to artifacts/ (or export an idle/completed session) before archiving.","Copy the artifacts to a stable staging directory first, then export from the snapshot.","Check the specific member named in the message for corruption or truncation and repair it."],"exampleFix":"// before\nexport_session_archive(&sessions_dir, live_session_id, out)  // logs still being written\n// after\nlet snapshot = copy_dir_to_tmp(artifacts_dir)?;\nexport_session_archive(&snapshot_sessions_dir, session_id, out)?;","handlingStrategy":"retry","validationCode":"let expected = fs::metadata(path)?.len();\nlet actual = fs::metadata(path)?.len(); // re-check just before archiving\nif expected != actual { /* source is changing; wait or snapshot first */ }","typeGuard":null,"tryCatchPattern":"match export_session_archive(...) {\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => retry_after_quiescing(3),\n    other => other,\n}","preventionTips":["Export only idle or completed sessions, or snapshot artifacts first.","Stop log writers/rotators during archive operations.","Avoid exporting sessions on network filesystems with stale metadata."],"tags":["archive","tar","race-condition","filesystem"],"backgroundTag":"checksum-mismatch","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"}