Skip to content

fix(active-session): 親 pid 起動時刻で呼び出し元 Claude Code を同定#6

Merged
akihidem merged 2 commits into
mainfrom
fix/active-session-parent-pid
May 11, 2026
Merged

fix(active-session): 親 pid 起動時刻で呼び出し元 Claude Code を同定#6
akihidem merged 2 commits into
mainfrom
fix/active-session-parent-pid

Conversation

@akihidem
Copy link
Copy Markdown
Owner

なぜこれを作ったか

複数 Claude Code ウィンドウを並行起動していると、findActiveSession が呼び出し元とは別のセッションを誤検出していた。具体的には get_recommended_action がコンテキスト膨張閾値 150k を超えたと警告するが、実態は呼び出し元セッションのコンテキストではなく、別ウィンドウ(または subagent)のセッションを映していた。

実測: 当該 Claude Code 自身のセッション (3f6af495-...) は 28 turns / uncachedSum 82k だが、findActiveSession は別の長寿命セッション (c8c746d0-..., 311 turns / 696k) を返していた。

原因は、findActiveSession が「mtime 降順 + 直近 5 分のイベントフィルタ」で active を決めていたところに、別ウィンドウや subagent の書き込みが頻繁に mtime / lastEvent を touch するため、呼び出し元とは無関係なセッションが mtime トップに上がってしまうこと。

何を入れたか

ファイル 変更
src/observers/claude_code.ts readProcessStartMs(pid) を追加(Linux 限定で /proc/<pid>/stat から wall-clock 起動時刻を算出)。resolveSessionByParentPid(logDir, parentPid, toleranceMs=120_000) を追加(親プロセス起動時刻と各 JSONL の first_ts を突き合わせ、最も近いものを返す)。findActiveSessionparentPid?: number 引数を追加し、解決成功時は resolution='parent-pid'、失敗時は既存の mtime-recent にフォールバック
src/mcp/resources.ts findActiveSessionprocess.ppid を渡す。ActiveSessionPayloadresolution フィールドを追加
src/mcp/tools.ts safeReadLatestSession 経由で process.ppid を渡す
tests/active-session.test.ts 新規。8 ケース(Linux 専用ロジック・tolerance・フォールバック挙動を網羅、非 Linux では return でスキップ)

設計判断

  • 親プロセス起動時刻 vs first_ts 突き合わせ を選んだ理由: stdio MCP は呼び出し元から spawn されるので process.ppid が Claude Code のプロセス ID になり、その起動時刻と session JSONL の first_ts は必ず数秒以内に一致する。確実な同定が可能。
  • 代替案:
    • lastUser 最新で選ぶ → 「ユーザが最後に発言した窓」になるだけで、呼び出し元の窓追従にはならない。並行ウィンドウで他方の入力に引っ張られる。却下。
    • /proc/<ppid>/fd で開いている jsonl を探す → Claude Code は JSONL を append のたび open/close するため fd には載らない。確認済み、却下。
    • MCP clientInfo._meta 経由で session id を受け取る → Claude Code 側が現状 session id を渡していない。本 PR では着手しない。
  • tolerance 120 秒: Claude Code 起動から最初の JSONL 書き込みまで実測 6 秒程度。120 秒は安全マージン。
  • Linux 限定: readProcessStartMsplatform() !== 'linux' で即 null を返す。macOS / Windows は今のところ Claude Code 利用者の主要環境ではないため、フォールバックの mtime-recent で従来通り動作(精度低下は受け入れ)。
  • 後方互換: findActiveSession の追加引数は optional で default null。watch.ts / timer/adaptive.ts は parent が Claude Code ではない(cogsync 自身が親)ため引数を渡さず、従来挙動を維持。

スコープ外

  • macOS / Windows 版の親起動時刻取得(ps -o lstart, wmic process 等)。需要が出てから別 PR で。
  • MCP _meta 経由で Claude Code から session id を直接受け取るプロトコル拡張。Claude Code 側の対応待ち。
  • cumulativeUncached の閾値 (150_000) 再調整。本 PR は active session の同定問題のみ修正。閾値は別途バックテストで。

検証方法

  • npm run typecheck が通る
  • npm test が 29/29 pass する(新規 8 + 既存 21)
  • 複数 Claude Code ウィンドウを起動した状態で、各 Claude Code から mcp__cogsync__get_recommended_action を呼び、それぞれ自セッションのトークン量を反映した rationale が返ることを確認
  • cogsync://state/active-session resource を読み、resolution: "parent-pid" が返ることを確認(並行ウィンドウありの環境)
  • standalone daemon (cogsync watch) で従来通り mtime-recent ベースで動くことを確認(回帰確認)

依存

なし。

akihidem and others added 2 commits May 12, 2026 01:44
複数 Claude Code ウィンドウ並行起動時、findActiveSession が mtime 降順 +
直近 5 分以内のイベントフィルタで「真にアクティブ」を選ぼうとしていたが、
subagent や別ウィンドウのイベントで mtime/lastEvent が頻繁に touch されるため、
呼び出し元 Claude Code とは別のセッションを誤検出し、cumulativeUncached が
他セッション分を映してしまっていた(実測: 自セッション 82k に対し 696k を返す)。

stdio MCP server は呼び出し元 Claude Code から spawn されるので、
process.ppid が Claude Code の pid になる。/proc/<ppid>/stat (field 22) と
/proc/stat:btime と CLK_TCK=100 から親の wall-clock 起動時刻を算出し、
各 session JSONL の first_ts と最も近いものを 120 秒 tolerance で選ぶ
ことで、呼び出し元自身のセッションを高精度に同定できる。

- observers/claude_code.ts: readProcessStartMs / resolveSessionByParentPid
  を追加。findActiveSession に parentPid 引数(任意)を追加し、解決成功時は
  resolution='parent-pid'、失敗時は既存の mtime-recent ヒューリスティクスに
  フォールバック
- mcp/resources.ts / mcp/tools.ts: process.ppid を渡す
- ActiveSessionPayload に resolution フィールドを追加(観測性のため)
- watch.ts / timer/adaptive.ts: 親が Claude Code ではない standalone daemon
  なので引数を渡さず、従来通り mtime-recent で動く

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #6 のローカル検証中、npm link 直後の /mcp 再接続で initialize 応答が
30s タイムアウトする事象を 1 度観測。直接実行 (cogsync mcp < /dev/null) では
コールド 0.66s で完了するため、本体ロジックの問題ではなく spawn 直後の
FS/シンボリックリンク race と推定。ただし MCP サーバ側で全く診断ログが
出ていなかったため、再発時に原因を絞れる材料を持っていなかった。

- mcp/server.ts: boot / config-loaded / handlers-registered / connected の
  4 段階で stderr に JSON line を出す。stdout は JSON-RPC 専用なので
  必ず stderr 側に書く(MCP プロトコル要件)。SIGTERM/SIGINT で確実に
  exit するよう明示ハンドラを登録(旧プロセス→新プロセスの race を減らす)
- index.ts: program.parseAsync に top-level catch を追加し、想定外の
  例外を必ず stderr に書いて非ゼロ終了する。MCP サーバが無音で落ちる事故を防ぐ

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akihidem akihidem merged commit 0ceb92d into main May 11, 2026
2 checks passed
@akihidem akihidem deleted the fix/active-session-parent-pid branch May 11, 2026 17:00
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