Skip to content

通常終了(Ctrl+C)でレイアウト状態の保存と全 dispose がスキップされる #84

Description

@goofmint

症状

実端末で tecode を Ctrl+C で終了すると、wireProcessExitshutdown()一度も実行されない。開いていたタブ/レイアウトは保存されず、全サービスの dispose も走らない。

根本原因

shutdown() は SIGINT / SIGTERM にのみ紐付いている(packages/cli/src/main.ts)。

for(constsignalof["SIGINT","SIGTERM"]asconst){process.once(signal,()=>{voidshutdown().finally(()=>process.exit(0));});

ところが通常の対話終了では SIGINT が発生しない

  1. createCliRenderer()stdin.setRawMode(true) を呼ぶ。Node の raw mode はシグナル生成を無効化するため、Ctrl+C は \x03 という通常の入力バイトとして届く。
  2. ピン留めされている @opentui/core@0.1.107 は、これを自前の exitOnCtrlC キー処理経路(既定 truematchesKeyBinding で判定)で受け、renderer.destroy() を直接呼んでプロセスを終える。
  3. その結果 process.once("SIGINT", ...) は発火せず、shutdown() は通常終了パスにおいて実質デッドコードになっている。

失われている処理

awaitroot.layoutState.flush();// ← レイアウト状態の永続化root.config.dispose();root.chordPendingIndicator.dispose();root.chordMachine.dispose();root.findService.dispose();root.editorSession.dispose();// ... 以下 15 個以上の disposeawaitroot.hostRef.current?.disposeAll();// ← 拡張機能の deactivate

とくに layoutState.flush() が走らないため、セッションのレイアウトが毎回静かに失われる。拡張機能の deactivate も呼ばれない。

補足

destroy()cleanupBeforeDestroy()setRawMode(false) と stdin リスナ解除を正しく行っているため、端末の復元自体には問題は見つかっていない。あくまで tecode 側のクリーンアップが呼ばれない点が本件。

なお Issue #82 の調査中に判明したもので、#82 の修正(テキスト面への初期フォーカス)とは独立している。

修正方針の候補

  • createCliRendererexitOnCtrlC: false を渡し、Ctrl+C を tecode 自身のキーバインドとして扱って shutdown() を通す。終了コマンドが明示的になる利点もある(現状 ctrl+q は未バインド)。
  • あるいは OpenTUI の終了フックに shutdown() を接続する。

前者なら「終了前に未保存の変更を確認する」といった将来の要求にも素直に繋がる。ただし Ctrl+C が効かなくなる実装ミスは致命的なので、実端末での確認が必要。

検証上の注意

この不具合は実 TTY がないと再現できない。開発サンドボックスでは main.ts が stdout 非 TTY 時に no-op の renderShellHeadless を強制するため、そもそも実レンダラ経路が走らない。テストで固定するなら、exitOnCtrlC 経路ではなく「shutdown() が終了時に呼ばれること」を注入可能なシームとして検証する形になる。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions