エラーを出さずに壊れているものが、いちばん時間を溶かす

出社前の十分と、帰宅後の数時間。それだけの時間の中で、詰まっていた大半はこれだった。プログラムは正常終了する。画面は赤くならない。でも動いていない。

エラーが出てくれるなら、まだいい。文言をそのまま検索すれば、たいてい誰かが同じ壁に当たっている。厄介なのは、失敗が失敗の顔をしていないときだ。

声でClaude Codeを起動しようとして、平日の夜が終わった

やりたかったことは単純だった。「起動して」と話しかけたら開発環境が立ち上がる。それだけだ。

手元にあるものだけで組めそうだった。宅録用のオーディオインターフェースと、Windowsに最初から入っている日本語の音声認識エンジン。買い足すものはない。出社前に少し触った時点では、その日のうちに終わると思っていた。

実際には、音声認識そのものに使った時間はほとんどなかった。溶けたのは全部、以下の四つだった。

1. バッチファイルに日本語を書いたら、行が真っ二つに割れた

Windowsで起動用のバッチファイルを書いた。コメントに日本語を入れた。それだけだ。

実行したら、書いた覚えのない文字列がコマンド名として怒られた

'繝ッ繝シ繝牙セ・ゥ・-' は、内部コマンドまたは外部コマンド…として認識されていません

'-ExecutionPolicy' は、内部コマンドまたは外部コマンド…として認識されていません

二行目がおかしい。-ExecutionPolicy は行の途中にあるはずのもので、単独のコマンドとして実行されるはずがない。

理由はこうだった。

僕が書いたもの    → UTF-8 の日本語

cmd が読んだつもり  → CP932(日本語環境の既定)

文字化けするだけでは済まず、途中のバイトが区切りとして解釈され

行が分裂して、別のコマンドになる

先頭に文字コードを指定する命令を置いても、手元では防げなかった。バッチファイルの中身は、コメントも含めて英数字だけにする。日本語が必要な処理は別のファイルに追い出す。ファイル名が日本語なだけなら、今回は問題にならなかった。

2. ファイルの先頭3バイトが無いだけで、日本語が静かに化けた

日本語の処理はPowerShellのファイルに追い出した。起動の合図にする言葉を、そこに書いた。

スクリプトは正常に起動した。待機画面もきれいに出た。エラーはゼロ。

そして、何を話しかけても永遠に反応しなかった。

Windows PowerShell 5.1 は、BOMが付いていないファイルを、システムの既定の文字コードとして読む。日本語環境ではそれがCP932(Shift-JISのMicrosoft拡張)になる。僕はUTF-8で書いていたので、日本語の文字列が化けた状態で登録されていた。登録されている言葉と、こちらが発音している言葉が、そもそも別物になっていた。

これがいちばんタチが悪かった。「動いていない」ではなく、「正常に待機している」という顔をしているからだ。

確かめ方は単純で、ファイルの先頭3バイトを見るだけでいい。

ef bb bf が付いている  → UTF-8として読まれる。安全

何も付いていない     → UTF-8で書いたつもりなら、化けている

ここは条件付きなので補足しておくと、はじめからCP932で保存してあるファイルは、BOMが無くても正しく読まれる。問題になるのは「UTF-8で書いたのにBOMが無い」という組み合わせのときだけだ。

3. 検証コードの方が壊れていて、通っていないのに「通った」と読んだ

これは自分のミスなので、いちばん書きにくい。

動作確認のために、テスト用のデータをプログラムに流し込んでいた。そのデータが、そもそも壊れた形式だった。受け取った側は読めずに黙って終了する。そして正常終了として返ってくる。

僕が見ていたもの    → 終了コード 0 = 成功

実際に起きていたこと  → 読めずに素通りして終了

試験が通ったのではなく、試験が素通りしていた

本体を疑う順番はいくらでも思いつく。設定、権限、パス、依存関係。でも「確かめている自分の手つきが壊れている」は、疑う対象の候補に入っていなかった。結果は毎回「異常なし」の顔でやってくる。

4. 言語を指定していなかったので、日本語を喋ったのに英文が返ってきた

Claude Code には音声入力の機能がある。日本語で話しかけた。

入力欄に、こう入った。

to show us and share this

意味の通る英文だった。文字化けなら一目で分かる。でもこれは、そこそこ自然な英語として成立している

原因は、設定ファイルに言語の指定が無かったことだった。未指定のときは英語として扱われる。日本語の音が、英単語の並びに当てはめられていた。

壊れているのに、出力がもっともらしい。このパターンがいちばん発見が遅れる。

なぜ気づけないのか

四つとも、共通点がひとつある。

壊れている    → 何も言わない

正常に見える   → 終了コード0 / 待機中 / それらしい文

気づくきっかけ  → 無い

エラーメッセージは基本的に、開発者が「ここで失敗しうる」と想定した場所に置かれる。裏を返せば、想定の外で壊れたとき、プログラムは黙る。

そして黙られると、人間は「動いている」と読む。

今のところ、こうしている

結局この日を動かしたのは、原因を推理することではなかった。見えないものを見えるようにする方だった。

音声認識が反応しなかったとき、最終的に効いたのは、途中経過を表示に出したことだ。

レベル [##########   ] 42   → マイクは生きているか

[声を検出] …認識中        → 音声として拾えたか

[棄却] "くー起動" (0.35)      → 言葉として寄らなかったか

[起動] "くー起動" (0.96)      → 通ったか

四段のうちどこで止まるかで、原因が一意に決まる。レベルが動かないなら機器の問題。声を検出しないなら音量の問題。棄却が並ぶなら言葉の問題。

推理しなくてよくなった。原因を当てにいくのではなく、分岐が見える場所を作ってから一歩進む。これが結果的にいちばん速かった。

もうひとつ、後半に気づいたことがある。思い込みは、無言の失敗よりさらに手強い。

外から接続する仕組みが、手元の端末の一覧に見当たらなかった。落ちているのかと何度も確認したが、プロセスはずっと生きていた。「会話の一覧に並ぶもの」だと思い込んでいて、実際は「機器」として別の欄に登録されていた。

これを解決したのは、実機の画面を一枚見せたことだった。言葉で往復していた三十分が、画像一枚で終わった。動いていないものを探している間、それはずっと動いていた。

これからどうなるか

AIに任せられる範囲は広がっている。この日の作業も、大半は自分で一行ずつ書いてはいない。

ただ、エラーが出ない失敗は、AIにも見えない。渡される情報が「終了コード0」なら、AIも「成功した」と読む。実際この日、僕は自分で用意した検証の結果を信じて、通っていないものを「通った」と判断している。

だから当分のあいだ、「本当にそれは動いているのか」を確かめる工程だけは、人間の側に残る気がしている。書く速度が上がるほど、確かめる工程の相対的な重みは増していく。

作ることが安くなると、確かめることが高くなる

おわりに

一日の終わりに残ったものは、そんなに大したものではない。話しかけると環境が立ち上がり、返事が音声で返り、外出先からも同じ機械を呼べる。新しい発明はひとつもない。元々あったものを、順番に繋いだだけだ。

それでも、朝の時点では「できるか分からない」に分類していた項目が、夜には動いていた。詰まったのは技術の難所ではなく、文字コードと、既定値と、自分の思い込みだった。

もし今、何かが「動かないのに理由が分からない」状態なら、原因を推理する前に、途中の状態が見える表示をひとつ足してみてほしい。それだけで、探す範囲が一気に狭まることがある。

みなさんは、エラーを出さずに壊れているものを、どうやって見つけていますか。