Skip to content

Cast file loses header/tail (0-byte or truncated .cast) when the recording process dies abnormally - StreamWriter buffering defeats incremental writes #6

Closed

Description

@soroshsabz

Summary

The cast file is written through a buffered StreamWriter without AutoFlush and without a flush after the header line. If the recording host process dies abnormally (window closed, crash, taskkill), the recording loses everything still in the buffer - for short recordings that means a 0-byte cast file without even the JSON header, and for long recordings a file that ends mid-line with unterminated JSON. This contradicts the documented behavior that the cast "is written incrementally" and stays playable.

Environment

  • Bsn.Host 0.1.0 (PowerShell Gallery)
  • Windows 11 10.0.26300, PowerShell 7.6.9 host
  • Killing the recording pwsh externally (or closing the console window) during a recording

Repro A - short recording loses everything

Start-InteractiveTranscript -Command 'ping -n 60 127.0.0.1' -Path .\x.cast
# from another window, a few seconds in:
Stop-Process -Name pwsh   # or close the console window

Result: x.cast is 0 bytes - the header (written by the CastWriter constructor) never reached disk because the default 1 KB character buffer was never filled and Dispose() never ran.

Repro B - long recording loses its tail

A ~75 s opencode TUI recording produced a 2.6 MB cast with 7384 events; the host was killed while the TUI was still running. The file's last line is truncated mid-JSON (Unterminated string passed in when parsing), so the final events are lost and strict parsers reject the file.

Root cause

src/Bsn.Host/src/PtyRecorder.cs, CastWriter:

writer = new StreamWriter(path, false, new UTF8Encoding(false));
writer.NewLine = "\n";
// header written, but never flushed

StreamWriter buffers ~1 KB of characters; nothing is flushed until the buffer fills or Dispose() runs. The engine's finally block only runs on orderly termination.

Suggested fix

  • writer.Flush() (or writer.AutoFlush = true) immediately after writing the header line, so a valid header is always on disk.
  • AutoFlush = true for event lines (they are small and latency-tolerant), or at minimum flush after each event. The volume is trivial compared to correctness for an audit-oriented feature.

With per-line flushing, an abnormally terminated recording stays a valid asciicast file that simply ends at the last completed event.

Activity

  1. soroshsabz commented on Nov 6, 2026

    @soroshsabz
    MemberAuthor

    Evidence artifacts: https://gist.github.com/soroshsabz/ef146d32218dee62b4b88555f8f2d2ce

    • zero_byte_cast_listing.txt - directory listing proving the killed ping recording produced a 0-byte .cast (the empty file itself cannot be attached to a gist, so the listing stands in for it)
    • D2_tail_excerpt.txt - last 300 characters of a 2.6 MB cast whose host was killed mid-session: the file ends mid-JSON with an unterminated string
  2. added theissue type on Nov 6, 2026
  3. self-assigned this
    on Nov 6, 2026
  4. added 2 commits that reference this issue on Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions