init()(output.rs の 561〜567 行)と restore()(output.rs の 517〜524 行)。証拠、bun が動いているあいだのコードページ: 932 / 437(T1)console.log の出力は直すが、動いているあいだは窓を 65001 にしたまま(932 / 437)、強制終了では 65001 が残り(932 / 437)、fs.writeSync(1) ・ fs.write(1) ・ Bun.write(Bun.stdout) は化け(932 / 437)、戻し方は、順番を決めない形でまれに競合に負ける(時系列)依頼(bun#44693): コンソールのコードページを一切変えず、画面への出力はすべて WriteConsoleW で書いてほしい。判定は、窓が 932(日本語版 Windows)で始まっても 437(英語版 Windows)で始まっても同じだった。詳しくは 結論、試験の流し方は 条件 にある。
bun は、起動した瞬間(スクリプトが動く前)に窓の入力・出力コードページを 65001 に変え、終了時に「起動時に控えた値」へ戻す。この動きから、計画の害 H1〜H7 がすべて起きることを確かめた。node と deno はコードページを一度も変えず、すべてのケースで正しく動いた。
行は重要な順に並べた。PR #43662 のあとも残る害を先に置く。
| 害 | 内容 | bun ・ canary | bun PR #43662 | node ・ deno | 証拠(932 / 437) |
|---|---|---|---|---|---|
| H4 | 同じ窓の別のプロセスが巻き込まれる | ❌ 起きた | ❌ 起きる。bun が動いているあいだと、強制終了のあと、窓は 65001 | ✅ 起きない | 画面 / 画面(T4。隣のプロセス役が化け、chcp が 65001 を示す)。PR: ログ / ログ(T1、実行中は 65001) |
| H6 | 強制終了すると元に戻らない | ❌ 起きた | ❌ 起きる | ✅ 起きない | ログ / ログ(T3)。PR: 画面 / 画面 |
| H1 | パイプでつないだ相手の表示が化ける | ❌ 起きた | ⚠️ console.log と process.stdout は直った。fs.writeSync(1) ・ fs.write(1) ・ Bun.write(Bun.stdout) では起きる | ✅ 起きない | 画面 / 画面(T5 の ww)。PR: 画面 / 画面(T5 の ww を fs.writeSync で) |
| H5 | コードページが開始の値に戻った状態で、bun が UTF-8 のバイト列をそのまま書いて化ける | ❌ 起きた | ⚠️ H1 と同じ | ✅ 起きない | ログ / ログ(T5 の ww。受け手が書く直前のコードページが開始の値) |
| H3 | 終了後に 65001 が窓に残る | ❌ 起きた | ⚠️ 順番を決めたケースでは直ったが、順番を決めない形では、まれに入力側か出力側が 65001 のまま残る | ✅ 起きない | ログ / ログ(T2、cmd から bun → bun)。PR: ログ(932、見張り役つきの 19 回目。終了後は入力 932・出力 65001)。回数は 時系列 の表 |
| H2 | 結果が起動・終了の順で決まり、実行のたびに変わる | ❌ 起きた | ⚠️ 順番を決めない形で画面は毎回正しかったが、まれに 65001 が残る(H3) | ✅ 起きない | T5 の bun#43660 と同じ形。T5 の 4 通りの順で結果が 4 つに分かれる |
| H7 | bun 同士が直接つながっていなくても起きる | ❌ 起きた | ✅ 直った | ✅ 起きない | 画面 / 画面(T5 の relay、bun | node | bun) |
PR #43662 は、bun_sys::write を通る画面への出力を WriteConsoleW にし、終了時は控えた値が 65001 でないときだけ戻す。CI の Windows x64 のビルドを、同じ試験一式で流した。bun#43660 で報告した症状は直るが、起動時に 65001 にすることは残るため、共有の状態を変えることから起きる害は残る。
fs.writeSync(1) ・ fs.write(1) ・ Bun.write(Bun.stdout) は WriteFile のままなので、ww の順では化ける(T5)。PR の説明文とレビューにも、そう書かれているSetConsoleOutputCP と SetConsoleCP を呼ばない。437 や 932 の窓は、これまでどおり 65001 に書き換える。この資料の結果はこのコミットより前の版で測ったもので、このコミットでは bun#44693 の求める実装にならない。このコミットを含む版(先頭コミット 6b5271cb1)で、932 と 437 の全件を流し直したところ、PR の版の判定は 576eb251a のときと 1 件も違わなかった。順番を決めない回(asis)は 932 と 437 で 60 回ずつ、すべて正しく出て、コードページも戻った。強制終了で 65001 が残ることなど、PR のあとも残る害も変わらないパイプを流れるデータそのものは、どの組み合わせでも壊れなかった(T2)。害はすべて、窓のコードページという共有の状態を書き換えることから起きている。順番を決めたケースは、安定版と canary 版ですべて同じ判定になり(T6)、932 と 437 でもすべて同じだった。
PR #43662 での robobun の返信に出てきたもの。2026-10-10 の時点で、どちらも open で、マージされていない。
ReadFile ではなく ReadConsoleW で読む。いまは REPL ・ prompt() ・ alert() ・ confirm() ・ bun init ・ bun update -i ・ bun - が ReadFile で読み、入力側のコードページで変換される。Windows 10 / Server 2019 の conhost では、65001 での変換が ASCII 以外の入力を壊す。process.stdin ・ Bun.stdin ・ fs.readSync(0) は libuv を通るため影響を受けない。robobun によれば、起動時に 65001 にするのをやめるには、入力側がいまそれに頼っているため、この PR が先に要る。PR 自身は「壊れているのは ANSI 版の変換で、コードページではない」と書いているsrc/io/windows/ に移る。robobun によれば、これのあと fs.writeSync ・ fs.write ・ Bun.write の書き込みの手当ては src/sys/windows/fs.rs::write_at 1 か所で済む。直る不具合の一覧に「別のプロセスがコードページを戻した後、process.stdout.write が化けていた(console.log はまだ化ける)」があり、ここで測った害と合うinit()(output.rs の 561〜567 行)が GetConsoleOutputCP / GetConsoleCP の値を控え、SetConsoleOutputCP(65001) / SetConsoleCP(65001) を呼ぶrestore()(output.rs の 517〜524 行)が終了時に控えた値へ戻すリンクはいまの main を指す。T5 の時系列は、このコードとそのまま一致する。各プロセスは起動時に見えた値を控えるので、2 本目は 65001 を控える。
1 本だけを窓で動かし、画面に abc 東京大阪 xyz を出す。値は 入力/出力。
試験コード: T1(codepage.test.ts の 35〜51 行)
| ランタイム(開始) | 起動直後 | 書く直前 | 終了直前 | 終了後 | 画面 |
|---|---|---|---|---|---|
| bun 1.4.3 (932) | ❌ 65001/65001 | ❌ 65001/65001 | ❌ 65001/65001 | ✅ 932/932 | ✅ |
| bun 1.4.3 (437) | ❌ 65001/65001 | ❌ 65001/65001 | ❌ 65001/65001 | ✅ 437/437 | ✅ |
| bun canary (932) | ❌ 65001/65001 | ❌ 65001/65001 | ❌ 65001/65001 | ✅ 932/932 | ✅ |
| bun canary (437) | ❌ 65001/65001 | ❌ 65001/65001 | ❌ 65001/65001 | ✅ 437/437 | ✅ |
| bun PR #43662 (932) | ❌ 65001/65001 | ❌ 65001/65001 | ❌ 65001/65001 | ✅ 932/932 | ✅ |
| bun PR #43662 (437) | ❌ 65001/65001 | ❌ 65001/65001 | ❌ 65001/65001 | ✅ 437/437 | ✅ |
| node (932) | ✅ 932/932 | ✅ 932/932 | ✅ 932/932 | ✅ 932/932 | ✅ |
| node (437) | ✅ 437/437 | ✅ 437/437 | ✅ 437/437 | ✅ 437/437 | ✅ |
| deno (932) | ✅ 932/932 | ✅ 932/932 | ✅ 932/932 | ✅ 932/932 | ✅ |
| deno (437) | ✅ 437/437 | ✅ 437/437 | ✅ 437/437 | ✅ 437/437 | ✅ |
送り手と受け手を同時に起動し、受け手は受け取ったバイト列をファイルに書く。各セルは「バイト列、終了後のコードページ」を、932 で始めた窓と 437 で始めた窓の順に並べたもの。
試験コード: T2(codepage.test.ts の 53〜81 行)
起動元ごとのコマンドの組み立て: 58〜61 行
| 送り手 → 受け手 | cmd | pwsh 7 | Windows PowerShell 5.1 |
|---|---|---|---|
| bun → bun | 932: ✅ ❌ 437: ✅ ❌ | 932: ✅ ❌ 437: ✅ ❌ | 932: ✅ ✅ 437: ✅ ✅ |
| bun → node | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| bun → deno | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| canary → canary | 932: ✅ ❌ 437: ✅ ❌ | 932: ✅ ❌ 437: ✅ ❌ | 932: ✅ ✅ 437: ✅ ✅ |
| canary → node | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| canary → deno | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| pr43662 → pr43662 | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| pr43662 → node | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| pr43662 → deno | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| node → bun | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| node → canary | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| node → pr43662 | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| node → node | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| node → deno | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| deno → bun | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| deno → canary | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| deno → pr43662 | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| deno → node | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
| deno → deno | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ | 932: ✅ ✅ 437: ✅ ✅ |
? に置き換える(node | node でも同じ。5.1 自身の事象)。5.1 の列は node | node と同じバイト列なら ✅ とした。5.1 が間に入ると 65001 は残らなかった。5.1 は 2 本の bun を同時に動かさないため。同じ形を 5.1 経由で 20 回流して見張ると、20 回とも、送り手の bun が終わってから受け手の bun が起動した(間は 11〜22 ms。1 回目の watch.ndjson、results.jsonl)。寿命が重ならないので、一方が控える値に他方の 65001 が入らない画面に書いたあと待機している 1 本を taskkill /F で止める。
試験コード: T3(codepage.test.ts の 83〜102 行)
| ランタイム(開始) | 終了後のコードページ | chcp の表示 | 隣のプロセス役 |
|---|---|---|---|
| bun 1.4.3 (932) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun 1.4.3 (437) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun canary (932) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun canary (437) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun PR #43662 (932) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun PR #43662 (437) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| node (932) | ✅ 932/932 | ✅ 現在のコード ページ: 932 | ✅ |
| node (437) | ✅ 437/437 | ✅ Active code page: 437 | ✅ |
| deno (932) | ✅ 932/932 | ✅ 現在のコード ページ: 932 | ✅ |
| deno (437) | ✅ 437/437 | ✅ Active code page: 437 | ✅ |
bun は終了時に戻す作りのため、強制終了されると 65001 のまま残る(H6)。そのあと同じ窓で動く cmd は、自分のスクリプトを UTF-8 として読み違えて化ける(H4)。
bun | bun が普通に終わった後(2 本の寿命が重なり、送り手が先に終わる順)の窓。
試験コード: T4(codepage.test.ts の 104〜121 行)
| ランタイム(開始) | 終了後のコードページ | chcp の表示 | 隣のプロセス役 |
|---|---|---|---|
| bun 1.4.3 (932) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun 1.4.3 (437) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun canary (932) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun canary (437) | ❌ 65001/65001 | ❌ Active code page: 65001 | ❌ |
| bun PR #43662 (932) | ✅ 932/932 | ✅ 現在のコード ページ: 932 | ✅ |
| bun PR #43662 (437) | ✅ 437/437 | ✅ Active code page: 437 | ✅ |
| node (932) | ✅ 932/932 | ✅ 現在のコード ページ: 932 | ✅ |
| node (437) | ✅ 437/437 | ✅ Active code page: 437 | ✅ |
| deno (932) | ✅ 932/932 | ✅ 現在のコード ページ: 932 | ✅ |
| deno (437) | ✅ 437/437 | ✅ Active code page: 437 | ✅ |
bun の窓の画面(左が 932、右が 437):
abc 譚ア莠ャ螟ァ髦ェ xyz abc µ¥▒Σ║¼σñºΘÿ¬ xyz
--- tail --- --- tail ---
Active code page: 65001 Active code page: 65001
neighbor: ������� neighbor: caf�
node で同じことをした窓の画面(左が 932、右が 437):
abc 東京大阪 xyz abc 東京大阪 xyz
--- tail --- --- tail ---
現在のコード ページ: 932 Active code page: 437
neighbor: 東京大阪 neighbor: café
日本語版 Windows の chcp は、出力側が 65001 になると英語のメッセージ(Active code page)を出す。bun ・ canary とも、強制終了しなくても同じ窓の隣のプロセス役と chcp まで巻き込んだ(H4)。
2 本目の起動を約 1 秒遅らせ、終了の順は待ち時間で決める。2 本の寿命は必ず重なるようにした。各組 3 回。セルは「画面またはバイト列 / 終了後に開始のコードページ」を、932 と 437 の順に並べたもの。順番ごとの予想は 計画の T5 にある(ww は化けて 65001 が残る、wr と rw は正しい、rr は正しいが 65001 が残る)。
試験コード: T5(codepage.test.ts の 123〜210 行)
| 形と順番 | bun 1.4.3 | bun canary | bun PR #43662 | node | deno |
|---|---|---|---|---|---|
| 送り手が先に起動し、先に終わる (ww) 受け手は画面に書く | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| 送り手が先に起動し、先に終わる (ww) 受け手はファイルに書く | 932: ✅✅✅ / ❌❌❌ 437: ✅✅✅ / ❌❌❌ | 932: ✅✅✅ / ❌❌❌ 437: ✅✅✅ / ❌❌❌ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| 送り手が先に起動し、受け手が先に終わる (wr) 受け手は画面に書く | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| 送り手が先に起動し、受け手が先に終わる (wr) 受け手はファイルに書く | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| 受け手が先に起動し、送り手が先に終わる (rw) 受け手は画面に書く | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| 受け手が先に起動し、送り手が先に終わる (rw) 受け手はファイルに書く | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| 受け手が先に起動し、受け手が先に終わる (rr) 受け手は画面に書く | 932: ✅✅✅ / ❌❌❌ 437: ✅✅✅ / ❌❌❌ | 932: ✅✅✅ / ❌❌❌ 437: ✅✅✅ / ❌❌❌ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| 受け手が先に起動し、受け手が先に終わる (rr) 受け手はファイルに書く | 932: ✅✅✅ / ❌❌❌ 437: ✅✅✅ / ❌❌❌ | 932: ✅✅✅ / ❌❌❌ 437: ✅✅✅ / ❌❌❌ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| 間に node を挟み、送り手が先に起動して先に終わる (relay) 受け手は画面に書く | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ | 932: ✅✅✅ / ✅✅✅ 437: ✅✅✅ / ✅✅✅ |
| ww で、受け手は fs.writeSync(1) で書く (ww-fswritesync) 受け手は画面に書く | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ❌❌❌ / ✅✅✅ 437: ❌❌❌ / ✅✅✅ | 932: — 437: — | 932: — 437: — |
| ww で、受け手は fs.write(1) で書く (ww-fswrite) 受け手は画面に書く | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ❌❌❌ / ✅✅✅ 437: ❌❌❌ / ✅✅✅ | 932: — 437: — | 932: — 437: — |
| ww で、受け手は Bun.write(Bun.stdout) で書く (ww-bunwrite) 受け手は画面に書く | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ❌❌❌ / ❌❌❌ 437: ❌❌❌ / ❌❌❌ | 932: ❌❌❌ / ✅✅✅ 437: ❌❌❌ / ✅✅✅ | 932: — 437: — | 932: — 437: — |
bun 1.4.3 と canary の結果は、932 と 437 のどちらでも、すべての組で計画の予想と一致し、3 回とも同じだった。順番さえ決まれば結果は決まる。PR #43662 の版は、受け手が process.stdout で書くときはどの順でも正しかった。一方、受け手が同じ文字列を fs.writeSync(1) ・ fs.write(1) ・ Bun.write(Bun.stdout) で書く最後の 3 行は、どの版でも化ける。これらの API は WriteFile のままのため。この 3 行は bun の版だけで流している(Bun.write は bun にしか無い)。
このログ から。値は 入力/出力。w は送り手、r は受け手、probe は観測役の node。437 で流した回も、932 が 437 に替わるだけで同じ形になる(ログ)。
0ms probe 窓の前 932/932
45ms w 起動直後 65001/65001 ← 送り手が 932 を控えて 65001 にする
47ms w 書く直前 65001/65001
1093ms r 起動直後 65001/65001 ← 受け手は 65001 を「元の値」として控える
2050ms w 終了直前 65001/65001 → 送り手が終了し、控えた 932 に戻す
2854ms r 書く直前 932/932 ← 受け手は 932 の窓に UTF-8 のバイト列を書く(化ける)
2860ms r 終了直前 932/932 → 受け手が終了し、控えた 65001 に戻す
2989ms probe 窓の後 65001/65001 ← 65001 が残る
WriteConsoleW で渡していれば、コードページに関係なく正しく出たはずである(node ・ deno は 932 でも 437 でも正しく出している)bun#43660 と同じ形で、2 本を同時に起動し、順番を決めずに 10 回流した(PR #43662 の版は 60 回)。文字列は \u エスケープで渡した abc 東京大阪 xyz(deno は deno eval)。
bun -e "console.log('abc \u6771\u4eac\u5927\u962a xyz')" | bun -e "process.stdin.pipe(process.stdout)"
試験コード: bun#43660 と同じ形(codepage.test.ts の 140〜161 行)
| ランタイム | 開始 932: 画面 / 終了後 | 開始 437: 画面 / 終了後 |
|---|---|---|
| bun 1.4.3 | ❌❌❌❌❌✅✅❌❌❌ / ❌❌❌❌❌✅✅❌❌❌ 化けた回数 8 / 10 | ❌❌❌❌❌❌❌❌❌✅ / ❌❌❌❌❌❌❌❌❌✅ 化けた回数 9 / 10 |
| bun canary | ❌❌❌❌❌❌❌❌❌❌ / ❌❌❌❌❌❌❌❌❌❌ 化けた回数 10 / 10 | ❌❌❌❌❌❌✅❌❌❌ / ❌❌❌❌❌❌✅❌❌❌ 化けた回数 9 / 10 |
| bun PR #43662 | ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ / ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ 化けた回数 0 / 60 | ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ / ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ 化けた回数 0 / 60 |
| node | ✅✅✅✅✅✅✅✅✅✅ / ✅✅✅✅✅✅✅✅✅✅ 化けた回数 0 / 10 | ✅✅✅✅✅✅✅✅✅✅ / ✅✅✅✅✅✅✅✅✅✅ 化けた回数 0 / 10 |
| deno | ✅✅✅✅✅✅✅✅✅✅ / ✅✅✅✅✅✅✅✅✅✅ 化けた回数 0 / 10 | ✅✅✅✅✅✅✅✅✅✅ / ✅✅✅✅✅✅✅✅✅✅ 化けた回数 0 / 10 |
bun | bun)だけで、すべての順番を証明したものではないコードページが変わったまま残ったとき、窓にどのプロセスがいたかを見るため、同じコマンドを PR #43662 の版で、932 から 60 回流し直した。その間、同じ窓に node の見張り役をつないだ。見張り役は GetConsoleProcessList ・ GetConsoleCP ・ GetConsoleOutputCP を読み続け、プロセスの出入りとコードページの変化を、すべて UTC の時刻で記録した(watch.ndjson)。node はコードページを変えない(T1)。
同じ形を、見張り役の有無を変えて何度か流した。開始のコードページに戻らなかった回数は、日によって大きく変わる。うまくいった回も、選ばずにすべて載せる。
| 流した日(UTC) | 見張り役 | 回数 | 戻らなかった回 | 証拠 |
|---|---|---|---|---|
| 2026-10-07 | なし(全件の試験) | 20 | ❌ 1(932 の 6 回目、出力側) | log.jsonl(その時点の commit) |
| 2026-10-10 | あり | 60 | ❌ 3(19・45・51 回目) | results.jsonl |
| 2026-10-10 | なし(全件の試験) | 120 | ✅ 0(932 と 437 で 60 回ずつ) | 932 / 437 |
| 2026-10-11 | あり | 20 | ❌ 1(9 回目、出力側) | watch.ndjson、results.jsonl |
| 2026-10-11 | あり | 60 | ✅ 0 | results.jsonl |
| 2026-10-11 | あり(437 で開始) | 60 | ❌ 2(48・55 回目、入力側) | results.jsonl |
| 2026-10-11 | あり + 呼び出しの記録 | 40 | ❌ 3(6・15・21 回目、出力側) | 下の記録 |
437 で始めても同じように残った。残った側は 2 回とも入力側で、2026-10-10 の 45・51 回目と同じ形だった。
2026-10-11 の 9 回目は、下の 19 回目と同じ形だった。R は W が 65001 にした 7 ms 後に起動し、終了後は入力 932 ・ 出力 65001 で残った。以下は 2026-10-10 の 60 回の分析。
| 回 | 終了後(入力 / 出力) | 窓にいたプロセス | 時系列 |
|---|---|---|---|
| 19 | ❌ 932 / 65001 | バッチを動かす cmd.exe、前後の node の計測、chcp、bun 2 本。ほかにはいない | watch.ndjson |
| 45 | ❌ 65001 / 932 | watch.ndjson | |
| 51 | ❌ 65001 / 932 | watch.ndjson |
ほかの 57 回は 932 / 932 に戻った(results.jsonl、environment.json)。3 回の bun の行だけを抜くと(UTC)。W は送り手 bun -e "console.log(...)"、R は受け手 bun -e "process.stdin.pipe(process.stdout)":
run 19
13:39:12.205 attach W
13:39:12.208 codepage in 932 / out 65001
13:39:12.209 codepage in 65001 / out 65001
13:39:12.216 attach R
13:39:12.223 codepage in 932 / out 65001
13:39:12.226 detach W
13:39:12.226 codepage in 65001 / out 65001
13:39:12.263 codepage in 932 / out 65001
13:39:12.266 detach R
(left at in 932 / out 65001)
run 45
13:39:50.176 attach W
13:39:50.179 codepage in 932 / out 65001
13:39:50.181 codepage in 65001 / out 65001
13:39:50.189 attach R
13:39:50.192 codepage in 65001 / out 932
13:39:50.194 detach W
13:39:50.227 detach R
(left at in 65001 / out 932)
run 51
13:39:58.957 attach W
13:39:58.959 codepage in 932 / out 65001
13:39:58.960 codepage in 65001 / out 65001
13:39:58.970 attach R
13:39:58.974 codepage in 65001 / out 932
13:39:58.975 detach W
13:39:59.011 detach R
(left at in 65001 / out 932)
試した版は PR の先頭コミット 576eb251a。コードページを変えるのは、その output.rs だけ:
init()(output.rs の 581〜585 行)は、別々の 4 つの呼び出しで動く。出力側を控える、出力側を 65001 にする、入力側を控える、入力側を 65001 にするrestore()(output.rs の 536〜544 行)は、出力側、入力側の順に控えた値へ戻す。ただし控えた値が 65001 の側は戻さない呼ぶのが W と R だけなら、どの終わり方にも、それを生む順番は 1 つしかない:
ここまではソースと時刻からの推論。見張り役にはどのプロセスが呼んだかは見えず、R の「控える」と「変える」の間は読み取りの間隔より短い(19 回目で、出力側が 932 になってまた 65001 に戻った変化は記録に出ていない)。そこで次に、呼び出しを 1 つずつ記録した。
W と R をそれぞれ cdb(WinDbg 1.2610)の下で動かし、kernelbase の GetConsoleOutputCP ・ SetConsoleOutputCP ・ GetConsoleCP ・ SetConsoleCP にブレークポイントを置いた。呼ばれるたびに、プロセス ・ 値(Set は引数、Get は戻り値)・ 呼び出し元の番地を記録して、そのまま続行させた(trace-writer.ndjson ・ trace-reader.ndjson)。見張り役も同時に動かした。932 から 40 回流し、3 回で出力側が 65001 のまま残った。
6 回目(UTC)。W は pid 30744、R は pid 55480:
21:30:27.486 W GetConsoleOutputCP -> 932 (saves 932)
21:30:27.500 W SetConsoleOutputCP(65001)
21:30:27.509 W GetConsoleCP -> 932
21:30:27.511 W SetConsoleCP(65001)
21:30:27.514 R GetConsoleOutputCP -> 65001 (saves 65001)
21:30:27.525 W SetConsoleOutputCP(932) (W restores output)
21:30:27.527 W SetConsoleCP(932) (W restores input)
21:30:27.528 R SetConsoleOutputCP(65001)
21:30:27.537 R GetConsoleCP -> 932
21:30:27.539 R SetConsoleCP(65001)
21:30:27.594 R SetConsoleCP(932) (R restores input only)
(left at in 932 / out 65001)
…989ceb ・ …989cfc ・ …989d01 ・ …989d12、出力側の後始末は …989b72、入力側の後始末は …975511。どちらの bun も、同じ初期化と同じ後始末の箇所から呼んでいるGetConsoleOutputCP が 65001 を返し、R は出力側を戻さなかった。戻った 37 回のうち 14 回は、どちらかが一方の 65001 を控えていたが、もう一方の後始末がその後に来たため戻ったデバッガは呼ばれるたびに bun を止めるので、競合の割合はデバッガなしの回とは比べられない。時刻は 2 つのデバッガがそれぞれ記録したもので、2 本の間の前後は数 ms ずれることがある(21 回目は、記録上の時刻だけを見ると R が控えたのが W の後始末の後になっている)。値(何を控えたか)は、このずれの影響を受けない。デバッガは、出力を隠す前に名乗りを出すため、W の分はその名乗りがパイプに入り、R が画面に出している(screen.json)。
証拠(6 回目): trace-writer.ndjson、trace-reader.ndjson、watch.ndjson。40 回分:results.jsonl。コード: tools/50_run/asis-timeline.ts --trace、tools/50_run/cdb-trace.js、まとめは tools/50_run/summarize-trace.ts
| 項目 | 値 |
|---|---|
| OS | Windows 11 Home 10.0.26300(日本語版) |
| 窓 | ケースごとに conhost.exe で新しい窓を起こす。開始のコードページは、ほかと共有しないその専用の窓の中で chcp して 932 か 437 にする。そのあと試験はコードページに触らない |
| bun | 1.4.3+c6da4a4d3 |
| bun canary | 1.4.3-canary.1+bbdc5a519(準備の時点の最新) |
| bun PR #43662 | 1.4.3-canary.1+6b5271cb1。bun#43660 への修正で Open の PR #43662 の、CI の Windows x64 のビルド(先頭コミット 6b5271cb1、Buildkite のビルド 126042)。それまでの結果は 576eb251a(Buildkite のビルド 119166)で測った。窓の全プロセスの時系列と、そのあとの cdb の記録は、576eb251a のまま |
| node | v26.10.0 |
| deno | 2.9.7 |
| pwsh / Windows PowerShell | 7.6.6 / 5.1.26100.9549 |
| 送る文字列 | abc 東京大阪 xyz。化けると、932 では abc 譚ア莠ャ螟ァ髦ェ xyz、437 では abc µ¥▒Σ║¼σñºΘÿ¬ xyz |
| 流し方 | tools\10_setup\setup-runtimes.cmd が上のランタイムを公式の配布元から _bin\ に取り、tools\40_test\run-tests.cmd がそれを PATH の先頭に置いて全件を流す。PC に入っているものは使わない |
GetConsoleCP / GetConsoleOutputCP を読む。窓の前後は観測役の node が読む(T1 で node がコードページを変えないことを確かめている)ReadConsoleOutputCharacterW で窓の文字を読み取る。人の目には頼らないchcp と隣のプロセス役の cmd を流す。cmd は開始のコードページで書いてある(932 用は SJIS の neighbor: 東京大阪、437 用は CP437 の neighbor: café)✅ 期待どおり(あるべき姿と同じ) / ❌ 期待と違う。下の表の ✅ と ❌ は、1 つ 1 つがそのケースの証拠ファイルへのリンクになっている。画面(screen.json)、コードページの記録(log.jsonl)、受け取ったバイト列(out.bin)のどれか。
証拠はすべて research/evidence/ にあり、ケースごとに 1 フォルダ(cp<コードページ>/<bun か bun-canary>/<流した道具>/<ケース>/)。各フォルダには、流したバッチ(run.cmd)・ログ・画面と、あれば受け取ったバイト列がある。ログには日時も pid も無いので、2 回の結果を diff で比べられる。実行日時と道具の版は、回ごとの environment.json にだけある。
リンクはすべてコミット 5b9f56b を指すので、行番号はずれない。
%~dp0 からの相対にした ASCII だけのバッチを書くbun:ffi ・ koffi ・ Deno.dlopen で、読むだけ86007f8 の版)、中継役(hold.ts)