Skip to content

fix(tui): a copied NUL byte no longer panics the TUI on Windows - #876

Merged
kevincodex1 merged 1 commit into
Gitlawb:mainfrom
gnanam1990:fix/clipboard-nul-panic
Aug 7, 2026
Merged

fix(tui): a copied NUL byte no longer panics the TUI on Windows#876
kevincodex1 merged 1 commit into
Gitlawb:mainfrom
gnanam1990:fix/clipboard-nul-panic

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes #875

What was wrong

syscall.StringToUTF16 panics rather than returning an error when handed a string containing a NUL — documented standard-library behaviour — and github.com/atotto/clipboard's Windows path calls it directly. One NUL anywhere in a copied transcript selection therefore killed the whole program:

panic(...)
syscall.StringToUTF16(...)  syscall_windows.go:33
github.com/atotto/clipboard.writeAll({0x…, 0xb77})
…tui.model.handleTranscriptSelectionMouse.copyTranscriptSelectionCmd.func1()
zero: tui error: program was killed: program experienced a panic

The NUL gets through because ansi.Strip removes escape sequences and leaves C0 bytes untouched. The transcript carries NULs from two independent directions:

  • Tool output — a binary file read, or git's -z NUL-separated listings, which this repo itself parses in files_git_sweep.go:134
  • Zero's own card protocol — row prefixes are literally "\x00command-card\x00" and "\x00plan-card\x00"

The fix

Sanitise once at the write, not per source. The sources are unbounded — any tool result a user can select from is one — so a fix per source is a fix the next tool re-breaks. clipboardSafeText drops C0 controls (keeping \n and \t, which are legal clipboard content and the normal case for a multi-line selection), applied before both destinations.

The OSC52 fallback needed it too: a stray control byte terminates the escape sequence early and silently corrupts the copy. That is very likely why this went unnoticed off Windows — the identical NUL produces a bad copy there rather than a dead process.

Verification

  • TestNulNeverReachesTheClipboard — four real sources (command-card prefix, plan-card prefix, binary tool output, git -z listing); asserts no NUL survives and that readable content is not dropped, so this sanitises rather than truncates.
  • TestClipboardSanitisingKeepsLayout — clean multi-line text with tabs is returned byte-identical; other C0 bytes are removed.
  • Mutation-checked: removing the clipboardSafeText call makes the panic-guard test fail.
  • make fmt-check, go vet ./..., go test ./..., release build + smoke, git diff --check all clean. GOOS=windows go build ./... and GOOS=windows go vet clean.

Two pre-existing failures on main are unrelated and unaffected: TestRunDoctorFormatsRedactedProviderDiagnostics and TestRunDoctorConnectivityProbesProvider fail identically with this change stashed.

I could not execute the Windows path itself (no Windows machine); the crash mechanism is pinned by the standard library's own documented contract and the reported stack, and the sanitiser is proven by the tests above.

Summary by CodeRabbit

  • Bug Fixes
    • Improved clipboard copying by removing unsafe control characters while preserving readable text, line breaks, and tabs.
    • Prevented copied content from being truncated, corrupted, or causing errors when it contains binary data or special Git output.
    • Copy status counts now accurately reflect the sanitized clipboard content.

syscall.StringToUTF16 PANICS rather than returning an error when the
string contains a NUL — documented standard-library behaviour — and
github.com/atotto/clipboard's Windows path calls it directly. So one NUL
anywhere in a copied transcript selection killed the whole program:
"zero: tui error: program was killed: program experienced a panic",
reported from a real Windows machine.

The NUL reaches the clipboard because ansi.Strip removes escape
SEQUENCES and leaves C0 bytes untouched, and the transcript carries them
from two independent directions: tool output (a binary read, git's -z
NUL-separated listings — which this repo itself parses in
files_git_sweep.go) and Zero's own card protocol, whose row prefixes are
literally "\x00command-card\x00" and "\x00plan-card\x00".

Sanitised at the WRITE rather than at each source, because the sources
are unbounded: any tool result a user can select from is one, and a fix
per source is a fix the next tool re-breaks. The same pass protects the
OSC52 fallback, where a stray control byte terminates the escape
sequence early and silently corrupts the copy instead of crashing —
which is likely why this went unnoticed off Windows, where the identical
NUL produces a bad copy rather than a dead process.

\n and \t survive: a multi-line selection is the normal case and both
are legal clipboard content.

Fixes Gitlawb#875
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f46cb0b1-975b-48be-98b8-9bd94d468308

📥 Commits

Reviewing files that changed from the base of the PR and between cd0eb19 and b2c1e22.

📒 Files selected for processing (2)
  • internal/tui/clipboard_nul_test.go
  • internal/tui/transcript_selection.go

Walkthrough

Clipboard selections now remove unsafe control characters before native clipboard and OSC52 writes. New tests cover NUL bytes from transcript sources and verify that readable content, newlines, and tabs remain intact.

Changes

Clipboard safety

Layer / File(s) Summary
Sanitize clipboard selections
internal/tui/transcript_selection.go
Clipboard sanitization removes C0 controls and DEL while preserving newlines and tabs. The copy command sanitizes text before both clipboard destinations.
Validate sanitized clipboard content
internal/tui/clipboard_nul_test.go
Tests cover NUL removal from card, plan, binary-output, and git-listing content. Tests also verify preservation of readable content, newlines, and tabs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: anandh8x, pierrunoyt, vasanthdev2004

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary fix: preventing Windows TUI panics when copied transcript text contains NUL bytes.
Linked Issues check ✅ Passed The implementation sanitizes NUL and other C0 controls at the clipboard boundary, preserves layout, supports both clipboard paths, and adds regression tests for issue [#875].
Out of Scope Changes check ✅ Passed The code and tests directly support the linked issue and stated objectives; no unrelated changes are identified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Good diagnosis and the right place to fix it.

I checked the structural question, which is whether anything can still reach a clipboard write unsanitised: three callers of copyTranscriptSelectionCmd (composer.go:253, model.go:4741, transcript_selection.go:1458), both write sites inside it, and the sanitise happens before the closure so the OSC52 fallback is covered too. Nothing bypasses it. Stripping at the write rather than per source is the correct call, and your reasoning for it is the argument I would have made.

One nit, and it is the tests rather than the fix. They exercise clipboardSafeText directly, so they pin the helper and not the wiring. I deleted the text = clipboardSafeText(text) line at the call site and both tests still passed, which means a later refactor can drop it and reintroduce the panic with CI green. Worth a seam that lets a test drive copyTranscriptSelectionCmd and assert no NUL reaches the native write, even something as small as taking the clipboard writer as a parameter.

Not blocking, since I verified the wiring by hand this time. But this exact shape has bitten us three times recently, most memorably #866, which shipped as a complete no-op with six green tests because they all called the helper instead of the path.

Minor question rather than a request: \r is stripped along with the other C0 bytes. On Windows the clipboard convention is CRLF, so if any transcript row carries CRLF a paste will arrive LF-only. Probably irrelevant since the rows look LF-native, but you are closer to that code than me.

@Vasanthdev2004
Vasanthdev2004 requested a review from jatmn August 7, 2026 09:23
@Vasanthdev2004

Copy link
Copy Markdown
Collaborator

@jatmn this one needs a third approval and it is small.

It is a crash fix: a copied NUL takes the whole TUI down on Windows, because atotto/clipboard calls syscall.StringToUTF16, which panics rather than returning an error. It is reachable from ordinary use rather than exotic input, since this package's own card row prefixes are literally "\x00command-card\x00" and ansi.Strip leaves C0 bytes alone. It was hit on a real Windows machine while copying output.

I approved it, and I checked the coverage question by hand rather than reading the diff alone: three callers of copyTranscriptSelectionCmd, both clipboard write sites inside it, and the sanitise runs before the closure so the OSC52 fallback is covered too. Nothing reaches a write unsanitised.

One nit is already on the PR so you do not spend time rediscovering it: the tests exercise clipboardSafeText directly, so they pin the helper and not the wiring. I deleted the call-site line and both still passed. I did not block on it.

@kevincodex1
kevincodex1 merged commit cae0269 into Gitlawb:main Aug 7, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Copying a transcript selection containing a NUL byte panics the TUI on Windows

4 participants