[BUG] /export produces 0-byte files and empty clipboard — rendering pipeline returns empty string
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
/export consistently produces empty output. Both the file and clipboard paths generate 0 bytes of content. The command appears to succeed — the "Conversation exported to: ..." message is displayed — but the resulting file is 0 bytes. Clipboard export similarly reports success while the clipboard is empty. This occurs regardless of conversation length, including long sessions with hundreds of messages.
What Should Happen?
/export test-output should produce a non-empty file containing the conversation transcript. /export → "Copy to clipboard" should place the conversation text on the system clipboard.
Error Messages/Logs
No error is displayed. The success message shows normally:
"Conversation exported to: test-output.txt"
But:
$ ls -la test-output.txt
-rw-r--r-- 1 user staff 0 Apr 9 19:30 test-output.txt
Steps to Reproduce
- Start a Claude Code session (CLI, Homebrew cask binary)
- Have any conversation (even a single exchange works)
- Run
/export test-output - Check file size:
ls -la test-output.txt→ 0 bytes - Or: run
/export, select "Copy to clipboard", paste → empty
Claude Model
Opus
Is this a regression?
Yes; /export worked in previous versions.
Claude Code Version
2.1.98 (Claude Code)
Platform
Anthropic API
Operating System
Multiple: macOS, also confirmed on Fedora Linux by @Cianidos
Terminal/Shell
iTerm2
Additional Information
I traced through the minified source in the npm package (cli.js from @anthropic-ai/claude-code@2.1.98). The rendering pipeline for /export is:
/export → agY() → ogY() → zi8() → igY() → c58()
Root cause: c58 (render-to-string) returns empty, and igY has no recovery path.
c58 renders the transcript via Ink to a PassThrough stream. It captures only the first data event:
async function c58(q, K) {
let _ = "", z = false, Y = new PassThrough();
if (K !== undefined) Y.columns = K;
return Y.on("data", (O) => {
if (z) return;
z = true;
_ = O.toString();
}),
await (await XB(createElement(FkY, null, q),
{ stdout: Y, patchConsole: false })).waitUntilExit(),
_;
}
The PassThrough has no columns/rows/isTTY properties (the Ink renderer defaults to 80×24, so dimensions aren't the direct cause). The FkY wrapper exits via setTimeout(exit, 0) after one render frame.
Then igY breaks on the first empty chunk with no fallback:
for (let j = 0; j < $; j += A) {
let H = await w([j, j + A]);
if (MO(H).trim() === "") break; // all-or-nothing exit
await _(H);
}
MO is stripAnsi. If the rendered frame is whitespace/ANSI-only, trim() yields "" and the entire loop exits immediately — zero content is collected.
Both export paths then silently write the empty string:
- File:
_i8(filename, "")→ 0-byte file + success message - Clipboard:
WP("")→pbcopywith empty stdin + success message
Three possible triggers (couldn't narrow further without debugger access):
A. Stream timing — The Homebrew cask binary runs on Bun (Bun.FFI / Bun.stripANSI confirmed in binary strings). Bun's PassThrough may not synchronously emit the data event on .write(). If it fires after waitUntilExit() resolves, _ stays "".
B. Empty screen render — renderFullFrame (the non-TTY path, since PassThrough.isTTY is undefined) iterates screen cells. If Yoga layout produces zero-height content, the output is only newlines → stripAnsi().trim() → "".
C. Render context — c58 creates a fresh Ink instance with a bare AppStateProvider (no initialState). If the transcript component reads any state from context rather than its direct props, it sees an empty default state.
Suggested fixes:
- Collect all
datachunks instead of only the first event - Use
continueinstead ofbreakinigY, or remove the early exit - Report empty export as a failure — don't show a success message for 0-byte output
13 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Note: this is still occurring as of Claude Code
2.1.100Still broken as of Claude Code
2.1.101Still broken on v2.1.104
Still broken on
2.1.105...Claude Code
v2.1.107still broken, on Fedora LinuxStill broken on Claude Code
v2.1.109on macOSStill broken on
v2.1.110Still broken on
2.1.112... not sure why a whole week isn't enough to fix this bug even after I provided concrete suggestions for how to do so in my initial submission...Confirmed that
2.1.114is also still broken.I also had this error, 0 byte exports. One workaround I found was to point Claude at its own error, which it then acknowledged after I deleted the empty files, and then asked it to do the export manually which gave me the correct files.
It reported that the raw session transcript is live at: project dir/BLAHBLAH.jsonl 15MB, JSONL, one message per line. You can grep it, or pipe through jq to extract text: jq -r 'select(.message.content) | .message.content' BLAHBLAH.jsonl > session.md
This has been fixed in a recent release — /export no longer produces 0-byte files. The fix will land in v2.1.115+. If you're still seeing this in the latest version, please comment with your version and repro and we'll reopen.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.