自作 macOS IME Akaza がスリープ復帰後に死ぬ問題を数週間追いかけたら、犯人は IME じゃなかった

僕は macOS 用の日本語 IME を自作していて(mac-akaza)、普段使いもしているのだけど、この IME には長らく悩まされてきたバグがある。macOS をスリープから復帰させると、しばらくして日本語変換が一切効かなくなるというもの。

これがまた嫌らしい壊れ方をするんだよね。

再起動すると治る。でも毎回リブートするのはつらい。困ったものです。

数週間、間違った犯人を追いかけていた

診断ログを仕込んで分かったのは、「復路(IME→アプリへの文字挿入)は無傷なのに、往路(OS→IME へのキーイベント配送)だけが死んでいる」ということ。activateServer みたいな制御メッセージは届き続けるのに、keyDown だけが来ない。

ここから僕は IMKit(InputMethodKit)の接続が腐る説を疑って、いろいろやった。

最後のやつが特にタチが悪かった。一度「治った」実績ができると、それが仮説として頭に焼き付くんだよね。

再発、そして全仮説が崩壊する日

先日また再発した。今回はライブで切り分けできるチャンスだったので、片っ端から試した。

操作 結果
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 の処理に詰まって、解放のメッセージが処理されないままになるらしい)。最新版でも未修正。

これで全部説明がつく。

おまけ: プロセスを殺しても解放されないことがある

「じゃあ ghostty を再起動すれば治るね」と思うじゃん。ところが再起動しても治らなかった。調べると、死んだ PID が Secure Input の保持者として残留していた。本来プロセス終了時に OS が自動解放するはずのものが、WindowServer のセッション状態ごと腐っていたのである。

この場合の解消方法は、画面をロック(Ctrl+Cmd+Q)してパスワードでロック解除。loginwindow が Secure Input を取得→解放する際に、腐った状態が上書きされる。実際これで直った。リブート不要。

IME 側でやれること

根本対策は IME 側からは打てない。DisableSecureEventInput() は自プロセスのカウンタを減らすだけで、他プロセスの保持には干渉できない。そもそも外部から無効化できたらセキュリティ機構として意味がないので、これは正しい設計。

なので mac-akaza には「検出して教える」機能を入れた。

これで次からは、2時間の調査が入力メニューを開く3秒に短縮されるという感じ。

教訓

ちなみにこの調査は Claude Code と一緒にやったのだけど、切り分けが全部空振りしたあとに「『activateServer は届くのに keyDown だけ来ない』を仕様として説明できるのは Secure Event Input では」と言い出したときはちょっと感動した。人間、自分の仮説に固執するからね。困ったものです。

Published: 2026-07-13(Mon) 13:50