Skip to content

fix(desktop): restart v1 terminal stream subscription on reconnect#174

Merged
MocA-Love merged 2 commits intomainfrom
fix/v1-terminal-retry-reconnect-stream
Apr 15, 2026
Merged

fix(desktop): restart v1 terminal stream subscription on reconnect#174
MocA-Love merged 2 commits intomainfrom
fix/v1-terminal-retry-reconnect-stream

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Apr 15, 2026

症状

接続エラー発生後の自動リトライで、見た目は `[Reconnected]` と表示されて成功するのに、その後の stdout / exit / error イベントが 一切届かない 状態になる。

ユーザーから見ると:

  • `tail -f /var/log/app.log` が途中で止まって見える
  • 長時間の `npm run build` の完了を検知できない
  • idle shell に戻ってもプロンプトが描画されない

ターミナル自体はアクティブで入力もできるが、返事が一切来ない。pane を閉じて開き直すまで復旧しない。

原因

`v1-terminal-cache.ts:231` で subscription がエラー終了すると、cache は subscription=null + streamReady=false にリセットされる:

```ts
entry.subscription = electronTrpcClient.terminal.stream.subscribe(paneId, {
// ...
onError: () => {
entry.subscription = null;
entry.streamReady = false;
// renderer 側に errorHandler 経由で通知
},
});
```

その後 `Terminal.tsx:287` の auto-retry `useEffect` が `handleRetryConnection` を呼ぶ。しかしこの関数は initial attach の `useTerminalLifecycle` と違って、`startStream` / `setStreamReady` / `markTerminalSessionReady` を一切呼ばない

結果:

  • `createOrAttach` は成功する (backend session は再生成される)
  • xterm に `[Reconnected]` が書かれる
  • でも cache は subscription=null のまま
  • main から送られてくる data / exit / error は誰も受け取らない

コード比較

initial attach (`useTerminalLifecycle.ts` onSuccess):
```ts
v1TerminalCache.startStream(paneId); // ← subscription 再生成
v1TerminalCache.setStreamReady(paneId);
markTerminalSessionReady(paneId);
```

retry reconnect (`useTerminalColdRestore.ts` handleRetryConnection.onSuccess):
```ts
setConnectionError(null);
currentXterm.writeln("[Reconnected]");
// ❌ startStream が呼ばれていない
```

再現手順

  1. `tail -f /var/log/system.log` を走らせる
  2. terminal daemon を再起動 (`sudo pkill -9 -f terminal-daemon` 等)
  3. 画面に `[Connection lost. Reconnecting...]` → `[Reconnected]` が出る
  4. `/var/log/system.log` に追記が発生しても、terminal に何も届かない

UX 影響

  • 中〜大。長時間実行中のコマンド結果を取り逃す。
  • `[Reconnected]` が誤ったフィードバックになっている。ユーザーは「直った」と思って、実際にはデータが届かない状態で待ち続ける。
  • idle shell / ログ tail / dev server watch で特に致命的。

修正内容

`handleRetryConnection.onSuccess` で initial attach と同じ 3 ステップ を呼ぶ。ただし cold-restore 経路は独自のフローを持つので gate する。

```diff
setConnectionError(null);
currentXterm.writeln("\x1b[90m[Reconnected]\x1b[0m");

+if (!result.isColdRestore) {

  • v1TerminalCache.startStream(paneId);
  • v1TerminalCache.setStreamReady(paneId);
  • markTerminalSessionReady(paneId);
    +}

if (result.isColdRestore) {
// ... 既存の cold restore 分岐 ...
}
```

既存機能への影響

退化なし

  • 既存の cold-restore 分岐は触らない (独自の handleStartShell が別途 readiness を更新するべき筋で、そちらは別 PR)
  • initial attach は変更なし
  • kill/exit 経路 (`TERMINAL_SESSION_KILLED`) も touch なし

新たなリスク

  • `v1TerminalCache.startStream` は内部で `if (entry.subscription)` を早期 return するので、万が一二重呼び出しになっても無害
  • `setStreamReady` も `if (entry.streamReady)` で guard 済み
  • `markTerminalSessionReady` は paneId ごとの idempotent な Map 操作

関連問題

PR#173 (cold restore snapshot の早期破棄) とは独立。両方マージされても衝突しない (変更箇所が `handleRetryConnection` と `handleStartShell` で別関数)。

検証

  • ✅ `bun run typecheck`
  • ✅ `bunx @biomejs/biome check`

FORK NOTE

upstream (superset-sh/superset) superset-sh#3348 由来の regression。upstream 側の修正が出たら合わせる想定。

Summary by CodeRabbit

リリースノート

  • バグ修正
    • ターミナル再接続時のセッション状態管理を改善しました。コールドリストア中のセッション準備状態の取り扱いを最適化し、再接続時の信頼性を向上させました。

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0eeb511-5e42-4529-ae01-f26d563d8acd

📥 Commits

Reviewing files that changed from the base of the PR and between b17cf69 and b013578.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalColdRestore.ts

📝 Walkthrough

Walkthrough

useTerminalColdRestore.tsのhandleRetryConnectiononSuccessハンドラに条件付きロジックを追加し、result.isColdRestoreがfalseの場合のみv1TerminalCache.markSessionReady(paneId)を呼び出すようにしました。冷復元時のセッション準備状態マーキングを回避します。

Changes

Cohort / File(s) Summary
Terminal Cold Restore Logic
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalColdRestore.ts
handleRetryConnectiononSuccessハンドラに条件チェックを追加。isColdRestoreがfalseの場合のみセッションをキャッシュで準備完了状態にマーク。冷復元時はその後のhandleStartShellで状態管理を行うようになりました。

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐰 ウサギのつぶやき...
コールド復元の道を分かれて
キャッシュは準備を待つまで
シェル起動の時を迎えて
状態は正しくマークされる
🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed タイトルはリコネクト時のターミナルストリーム subscription の再開というPRの主要な変更を明確に要約しており、簡潔でわかりやすい。
Description check ✅ Passed 説明テンプレートの「Description」「Related Issues」「Type of Change」「Testing」「Additional Notes」の各セクションに対応し、日本語で詳細な根本原因分析、再現手順、修正内容、既存機能への影響が記述されている。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/v1-terminal-retry-reconnect-stream

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@MocA-Love
Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@MocA-Love
Copy link
Copy Markdown
Owner Author

Codex 独立検証での指摘に対応 (9da543d28)

background codex exec のレビューで、!result.isColdRestore gate が狭すぎることが指摘されました:

初回 mount の cold restore は useTerminalLifecycle.ts:605-610result.isColdRestore 判定startStream / setStreamReady / markTerminalSessionReady を実行している。handleRetryConnection には同等処理がないため、reconnect が cold-restore を返すケースだけ stream 再開が抜ける。

修正内容

!result.isColdRestore gate を削除して、reconnect 成功時は常に stream を再開:

- if (!result.isColdRestore) {
-   v1TerminalCache.startStream(paneId);
-   v1TerminalCache.setStreamReady(paneId);
-   markTerminalSessionReady(paneId);
- }
+ v1TerminalCache.startStream(paneId);
+ v1TerminalCache.setStreamReady(paneId);
+ markTerminalSessionReady(paneId);

安全性

  • startStream / setStreamReadyv1-terminal-cache.ts:218-220, 252-255冪等 (already live / already ready を早期 return)
  • 初回 attach と同じ並び順になる (useTerminalLifecycle.ts:605-610)

検証

  • bun run typecheck
  • bunx @biomejs/biome check

@MocA-Love
Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@MocA-Love MocA-Love force-pushed the fix/v1-terminal-retry-reconnect-stream branch from 9da543d to d329b76 Compare April 15, 2026 09:20
@MocA-Love
Copy link
Copy Markdown
Owner Author

Codex 再レビュー対応 (`e1e8d7889`)

Codex の独立コードベース分析で、前回の修正 (gate 削除) が PR #167 の invariant を破る との指摘:

現在の実装だと `cold restore` 分岐でも無条件に `markSessionReady()` してしまい、main が PR #167 で入れた「real shell が立つまでは ready にしない」という前提を崩しています。

具体的問題:

  • cold-restore reconnect は backend session 不在で返る (`isColdRestore=true`)
  • そこで `markSessionReady()` を呼ぶと cache の `streamReady=true` になる
  • 後段で handleStartShell の spawn が失敗したり、その前に tab switch で remount すると、isReattach fast-path で createOrAttach がスキップされる
  • PR fix(desktop): v1 terminal typing dead after tab switch when cold restore cached #167 が直したはずの「文字打てない」バグが再発する可能性

修正

`!result.isColdRestore` gate を復活させました:

```diff

  • v1TerminalCache.markSessionReady(paneId);
  • if (!result.isColdRestore) {
  • v1TerminalCache.markSessionReady(paneId);
  • }
    ```

なぜ前回の Codex 指摘 (gate 狭い) と矛盾しないか

前回 Codex は `handleStartShell` 側に stream 再開処理が無い問題を指摘していました。PR #167 が main にマージされた際、handleStartShell.onSuccess に v1TerminalCache.markSessionReady() が追加されました (`useTerminalColdRestore.ts:219`)。

そのため cold-restore reconnect 経路は:

  1. handleRetryConnection.onSuccess → cold-restore branch → state set, return
  2. Terminal.tsx useEffect → handleStartShell 自動呼び出し
  3. handleStartShell creates real shell (skipColdRestore: true)
  4. handleStartShell.onSuccess → markSessionReady ← PR#167 がここで補完済み

end-to-end でカバーされているので、handleRetryConnection 側で gate 復活させても抜け落ちはありません。

検証

  • ✅ `bun run typecheck`
  • ✅ `bunx @biomejs/biome check`

When the cache-owned stream subscription dies (v1-terminal-cache.ts
onError nulls the subscription and resets streamReady), only the
initial attach path in useTerminalLifecycle re-runs startStream /
setStreamReady / markTerminalSessionReady. handleRetryConnection was a
standalone re-attach: it happily succeeded at the tRPC level, wrote a
"[Reconnected]" marker to xterm, and left the cache-owned stream in a
dead state.

The resulting terminal looked healthy but never received another byte
of stdout / exit / error. Long-running tail -f processes, dev servers,
and shells waiting on idle commands silently stopped updating and the
user had to restart the pane (or the whole app) to notice. The failure
mode was especially pernicious for background builds — "[Reconnected]"
gave the impression that recovery had worked.

Restart the cache subscription and mark readiness at the end of the
retry success path, mirroring the initial attach path. Gate the call on
!result.isColdRestore: cold-restore returns from main without a real
session, and the existing cold-restore block handles its own bookkeeping
(and the replacement shell will run through handleStartShell, which
already needs its own readiness plumbing — addressed separately).

FORK NOTE: upstream regression. Revisit when upstream ships a coherent
retry / cache-reset contract.
Codex review (PR#174): the previous unconditional markSessionReady()
call in handleRetryConnection.onSuccess broke the PR #167 invariant
that the cache must not flip to streamReady=true before a real backend
session exists. Cold-restore reconnect responses come back with
isColdRestore=true and no backend; setting the cache ready in that
state lets a subsequent tab-switch remount take the isReattach
fast-path and silently drop user keystrokes — exactly the bug PR #167
was meant to fix.

Restore the !result.isColdRestore gate. The cold-restore reconnect
path is still wired end-to-end because handleStartShell.onSuccess (in
main, after PR #167) calls markSessionReady once the replacement shell
spawns.
@MocA-Love MocA-Love force-pushed the fix/v1-terminal-retry-reconnect-stream branch from e1e8d78 to b013578 Compare April 15, 2026 11:51
@MocA-Love MocA-Love merged commit e557049 into main Apr 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant