自作 macOS IME Akaza がスリープ復帰後に死ぬ問題を数週間追いかけたら、犯人は IME じゃなかった
僕は macOS 用の日本語 IME を自作していて(mac-akaza)、普段使いもしているのだけど、この IME には長らく悩まされてきたバグがある。macOS をスリープから復帰させると、しばらくして日本語変換が一切効かなくなるというもの。
これがまた嫌らしい壊れ方をするんだよね。
- IME のプロセスは生きている
- 変換エンジン(Rust 製の akaza-server)も応答する
- エラーログは一切出ない
- なのにキーを打っても変換されず、素の英字がそのまま入る
再起動すると治る。でも毎回リブートするのはつらい。困ったものです。
数週間、間違った犯人を追いかけていた
診断ログを仕込んで分かったのは、「復路(IME→アプリへの文字挿入)は無傷なのに、往路(OS→IME へのキーイベント配送)だけが死んでいる」ということ。activateServer みたいな制御メッセージは届き続けるのに、keyDown だけが来ない。
ここから僕は IMKit(InputMethodKit)の接続が腐る説を疑って、いろいろやった。
killall AkazaIME→ 治らない- wake 時に IMKServer を作り直す予防実験 → main thread が詰まって逆効果
- TIS API で入力ソースを再有効化する PR → 効果なしでクローズ
make install(バンドル置換)→ 一度これで治ったので「OS の入力メソッド再登録が鍵だ!」と確信
最後のやつが特にタチが悪かった。一度「治った」実績ができると、それが仮説として頭に焼き付くんだよね。
再発、そして全仮説が崩壊する日
先日また再発した。今回はライブで切り分けできるチャンスだったので、片っ端から試した。
| 操作 | 結果 |
|---|---|
| killall(2回) | ✗ |
| make install(バンドル置換) | ✗ ← あれ? |
| 入力ソースを ABC→Akaza にトグル | ✗ |
| 別アプリ(Slack)で入力 | ✗ 全アプリで死んでる |
前回「効いた」はずの make install が効かない。つまりあれは偶然の一致だったわけ。数週間信じてた仮説が音を立てて崩れた瞬間である。
真犯人: Secure Event Input
ここで発想を変えた。「activateServer は届くのに keyDown だけ全アプリで届かない」を仕様として説明できるものはないか?
ある。Secure Event Input だ。
macOS には、パスワード入力中にキーイベントをキーロガーや IME から守る仕組みがある。どこかのプロセスが EnableSecureEventInput を有効にしている間、OS はキーイベントを IME に配送しない。制御メッセージは届き続ける。まさにこの症状。
保持者はこれで調べられる。
ioreg -l -w 0 | grep -o 'kCGSSessionSecureInputPID"=[0-9]*'
ps -p <PID> -o pid,lstart,command
出てきた犯人は ghostty(僕が使っているターミナル)だった。
ghostty は sudo や ssh のパスワードプロンプトを検出すると、自動で Secure Input を有効にしてくれる。親切機能なのだが、これを解放し損ねて握りっぱなしになるバグがある(ghostty-org/ghostty#10480、main thread が Core Animation の処理に詰まって、解放のメッセージが処理されないままになるらしい)。最新版でも未修正。
これで全部説明がつく。
- killall もバンドル置換も無効 → IME 側には何の問題もないから
- リブートで治る → ghostty が終了して Secure Input が解放されるから
- 過去の「自然回復」→ ghostty がたまたま解放した瞬間
- スリープ復帰と相関 → 復帰時のパスワード入力まわりで張り付きやすいだけで、発症は数時間後だったりする
おまけ: プロセスを殺しても解放されないことがある
「じゃあ ghostty を再起動すれば治るね」と思うじゃん。ところが再起動しても治らなかった。調べると、死んだ PID が Secure Input の保持者として残留していた。本来プロセス終了時に OS が自動解放するはずのものが、WindowServer のセッション状態ごと腐っていたのである。
この場合の解消方法は、画面をロック(Ctrl+Cmd+Q)してパスワードでロック解除。loginwindow が Secure Input を取得→解放する際に、腐った状態が上書きされる。実際これで直った。リブート不要。
IME 側でやれること
根本対策は IME 側からは打てない。DisableSecureEventInput() は自プロセスのカウンタを減らすだけで、他プロセスの保持には干渉できない。そもそも外部から無効化できたらセキュリティ機構として意味がないので、これは正しい設計。
なので mac-akaza には「検出して教える」機能を入れた。
- Secure Input が有効なら、ログに保持プロセスの PID とアプリ名を記録
- 入力メニューに「⚠️ 「Ghostty」が Secure Input を有効化中 — 日本語入力不可」と表示
これで次からは、2時間の調査が入力メニューを開く3秒に短縮されるという感じ。
教訓
- 「一度治った」は因果の証明にならない。偶然の一致は本当にある
- 自分のコードのバグに見えるものが、OS の仕様通りの動作であることがある
- 「エラーが出ない」のは、どこも壊れていないからかもしれない
- 日本語入力が突然全アプリで死んだら、まず
ioreg -l -w 0 | grep SecureInputを叩こう
ちなみにこの調査は Claude Code と一緒にやったのだけど、切り分けが全部空振りしたあとに「『activateServer は届くのに keyDown だけ来ない』を仕様として説明できるのは Secure Event Input では」と言い出したときはちょっと感動した。人間、自分の仮説に固執するからね。困ったものです。
Published: 2026-07-13(Mon) 13:50