You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bun.$ .text() mangles non-ASCII output on Windows into uXXXX without the backslash #47479
Bun.$'s .text() mangles non-ASCII output on Windows: every non-ASCII character comes back as uXXXX with the backslash dropped, so the result cannot be decoded back.
Repro
Bun 1.4.4, Windows 10.0.26300 (x64), cmd.exe as the shell.
ASCII is unaffected: await Bun.$cmd /c echo hello.text() → "hello\r\n".
The bytes on the pipe are correct
Bun.spawn reading the same command returns proper UTF-8, so the corruption is in the Bun.$ decode path, not in the child process or the console code page:
constp=Bun.spawn(['cmd','/c','echo 中文测试'],{stdout:'pipe'});constbuf=newUint8Array(awaitnewResponse(p.stdout).arrayBuffer());// e4 b8 ad e6 96 87 e6 b5 8b e8 af 95 0d 0anewTextDecoder('utf8').decode(buf);// "中文测试\r\n"
node:child_process is also correct on the same machine:
So the corruption happens before .bytes(), not in .text() specifically. The only correct route I found is Bun.spawn + reading stdout directly.
Why it matters
Bun.$ is the documented ergonomic way to run a command and capture output. On Windows, any command that prints non-ASCII — file paths with CJK characters, dir listings, git output with a localized message, a CLI that prints a Unicode table — silently returns garbage that looks like an escape sequence. It fails loudly only if you notice the missing backslash.
Environment
Bun v1.4.4 (38afee6c8)
Windows 10.0.26300 x64
This is not console code page: chcp is irrelevant here because the raw bytes on the pipe already decode correctly with TextDecoder('utf8').
Correction after more testing: the corruption is on the input side, not the decode side. My original report blamed .text() decoding. That was wrong. The command string itself is rewritten before it reaches the shell.
What actually happens
Bun.$ takes the template literal's literal text and escapes every non-ASCII character into uXXXX — with the backslash dropped — before spawning the child. The shell receives u4E2Du6587, not 中文.
Proof: make the shell write what it received to a file, then read the raw bytes.
If the child had received 中文 and only the reader mangled it, the file would contain the UTF-8 bytes e4 b8 ad e6 96 87. It does not — the file contains the ASCII bytes for u4E2Du6587. The child never saw the Chinese.
Two other facts that pin it down
Interpolation is not affected. Only literal text inside the tagged template is rewritten:
Unicode escapes are also rewritten (just lowercased), so the bug is a general "strip the backslash from any escape" behaviour rather than something specific to raw CJK:
I inferred "decode path" from Bun.spawn working and Bun.$ failing on the same command. That difference is real, but it does not locate the bug: Bun.spawn receives the string as a plain argv element and never goes through template-literal parsing, so it never hits the rewrite. The .bytes() result I cited as evidence that "corruption happens before .bytes()" was consistent with both hypotheses — I did not test the child's actual input.
The one test that settles it is the redirect-to-file one above. I should have run that first.
I cannot tell from the outside whether this is Windows-specific. The escaping happens before any child process exists, so it plausibly reproduces on every platform where Bun.$ parses a template literal — worth checking on Linux/macOS before deciding how to label it.
One concrete regression-test gap in current main: test/js/bun/shell/bunshell.test.ts, describe("unicode") currently covers Unicode through interpolation (echo ${whatsupbro}), and a separate backslash-escape case, but not a non-ASCII character written directly in the literal segment of a $ tagged template. That difference is exactly what the corrected repro isolates.
A focused regression alongside basic would compare await $echo 中文 against `const value = "中文"; await $`echo ${value}, expecting the same UTF-8 中文\n output. Run it on Windows (the reported failing platform) and at least one Unix target before deciding whether the parser bug is Windows-specific. The reporter's redirect-to-file test is still the stronger child-input discriminator; this proposed suite case specifically closes the presently untested literal-versus-interpolation path. This is a source-inspected test suggestion, not a claim that I reran Bun 1.4.4.
Activity
umhapal commentedon Oct 30, 2026
Correction after more testing: the corruption is on the input side, not the decode side. My original report blamed
.text()decoding. That was wrong. The command string itself is rewritten before it reaches the shell.What actually happens
Bun.$takes the template literal's literal text and escapes every non-ASCII character intouXXXX— with the backslash dropped — before spawning the child. The shell receivesu4E2Du6587, not中文.Proof: make the shell write what it received to a file, then read the raw bytes.
If the child had received
中文and only the reader mangled it, the file would contain the UTF-8 bytese4 b8 ad e6 96 87. It does not — the file contains the ASCII bytes foru4E2Du6587. The child never saw the Chinese.Two other facts that pin it down
Interpolation is not affected. Only literal text inside the tagged template is rewritten:
Unicode escapes are also rewritten (just lowercased), so the bug is a general "strip the backslash from any escape" behaviour rather than something specific to raw CJK:
Why my original diagnosis was wrong
I inferred "decode path" from
Bun.spawnworking andBun.$failing on the same command. That difference is real, but it does not locate the bug:Bun.spawnreceives the string as a plainargvelement and never goes through template-literal parsing, so it never hits the rewrite. The.bytes()result I cited as evidence that "corruption happens before.bytes()" was consistent with both hypotheses — I did not test the child's actual input.The one test that settles it is the redirect-to-file one above. I should have run that first.
Corrected repro
Bun v1.4.4 (38afee6), Windows 10.0.26300 x64.
Workaround
Interpolate the string instead of writing it literally:
Note on scope
I cannot tell from the outside whether this is Windows-specific. The escaping happens before any child process exists, so it plausibly reproduces on every platform where
Bun.$parses a template literal — worth checking on Linux/macOS before deciding how to label it.84dnnvbdvp-debug commentedon Nov 1, 2026
One concrete regression-test gap in current main:
test/js/bun/shell/bunshell.test.ts,describe("unicode")currently covers Unicode through interpolation (echo ${whatsupbro}), and a separate backslash-escape case, but not a non-ASCII character written directly in the literal segment of a$tagged template. That difference is exactly what the corrected repro isolates.A focused regression alongside
basicwould compareawait $echo 中文against `const value = "中文"; await $`echo ${value}, expecting the same UTF-8中文\noutput. Run it on Windows (the reported failing platform) and at least one Unix target before deciding whether the parser bug is Windows-specific. The reporter's redirect-to-file test is still the stronger child-input discriminator; this proposed suite case specifically closes the presently untested literal-versus-interpolation path. This is a source-inspected test suggestion, not a claim that I reran Bun 1.4.4.