1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

128日・838タスク・114億トークンを継続したCodex CLIセッションで`/side`障害を診断・復旧した記録

1
Posted at

検証環境: Windows 11 / WSL2 Ubuntu / Windows Terminal / Codex CLI

本記事は、同一のCodexセッションを数か月にわたって継続利用した実測記録と、その長期セッションで発生した/side切替障害の診断・復旧記録です。

コマンド例だけでなく、実際に取得したプロセス一覧、/procの値、メモリ監視CSV、Git/PRのcheckpoint結果も抜粋して掲載します。PIDや時刻は観測時点の値です。セッションIDや無関係な作業内容など、公開に不要な情報は省略しています。

はじめに

Codex CLIは、単発のコード生成だけでなく、長期の開発プロジェクトを継続して進められます。

今回対象となったセッションは、2026年4月10日に開始し、codex resumeを使いながら2026年8月17日まで継続していました。単一のSession IDのもとで、Issueの調査、設計、実装、テスト、PR作成、レビュー対応、CI監視、マージ、次のIssueへの移行を繰り返しています。

ローカルのCodexセッション記録から確認できた規模は次のとおりです。

項目 記録値
セッション開始 2026-04-10 17:22:36 JST
記録確認 2026-08-17 13:41:02 JST
セッション全体の暦上経過 128日20時間18分25秒
開始時のCodex CLI 0.118.0
後半で使用したCodex CLI 0.147.0
完了記録のあるtask 838件
完了taskの経過時間合計 371時間14分36秒
セッションログ 約1.33GB
累積Input tokens 11,373,888,419
うちcached input 11,051,251,968
累積Output tokens 33,928,455
うちreasoning output 7,176,913
累積Total tokens 11,407,816,874

Input tokensのうち、cached inputは約97.16%でした。

ローカル記録から集計した元の出力は、次のような内容でした。Session IDは公開原稿では省略しています。

セッション記録の実際の集計結果
セッション開始              2026-04-10 17:22:36.602 JST
開始時CLI                   Codex CLI 0.118.0
今回の再開turn開始          2026-08-17 13:38:55.723 JST
確認時刻                    2026-08-17 13:41:02 JST
セッション全体の暦上経過    128日20時間18分25秒
完了記録があるtask          838件
完了taskの所要時間合計      371時間14分36秒
セッションログサイズ        約1.33 GB

Input tokens               11,373,888,419
Cached input               11,051,251,968
Output tokens                  33,928,455
Reasoning output                7,176,913
Total tokens               11,407,816,874

この数字から、Codexが長期プロジェクトの作業コンテキストを保持し、数百の作業単位をまたいで開発を継続できることがわかります。

今回の記事の主題は、長期継続できたCodexセッションの運用実績と、その規模まで成長したセッションで発生した障害をどのように切り分け、復旧し、再開後の安定性まで確認したかです。

各記録値の範囲

各数字が表している範囲を補足します。

  • 128日20時間は、同じSession IDの初回開始から確認時点までの暦上の期間です。セッションを使っていなかった時間や、次にresumeするまでの空白も含みます。
  • 371時間14分36秒は、ログ上のtask_startedからtask_completeまでの経過時間を合算した値です。テスト、CI、外部コマンド、GitHubの応答待ちなども含まれます。
  • 838件は完了記録が残っているtask数です。端末停止やハングでtask_completeが残らなかった古いtaskが9件、確認時に進行中だったtaskが1件ありました。
  • 114億トークンはセッション全期間の累積値です。一度のコンテキストに114億トークンを投入したという意味ではありません。
  • 約1.33GBはローカルのセッションJSONLの規模です。
  • 障害時に観測した約4.4GiBのRSSは/procからその場で取得した値です。過去の最大RSSはセッションJSONLには保存されないため、ローカルログから再計算した値ではありません。

これらは、人間の作業時間や単一リクエストの大きさではなく、一つのCodexセッションが担った長期作業の規模を示す運用記録として扱います。


1. Codexが長期の開発を継続できる理由

1.1 自動compactionで作業の連続性を保つ

LLMには、一度の推論で参照できるコンテキストウィンドウの上限があります。

長い履歴をそのまま積み重ねるだけなら、いずれ上限に達します。Codexでは長期作業の途中で自動的にcompact処理が行われ、重要な作業文脈を引き継ぎながら新しいコンテキストで処理を継続できます。

OpenAIもCodexの長時間タスクについて、コンテキスト上限へ近づくと自動的にcompactionを行い、重要な文脈を維持しながら作業を継続する仕組みを説明しています。

今回のセッションでcompactionが何回行われたかは集計していません。それでも、単一のコンテキストには収まらない累積11.4B tokensの処理記録を持ちながら、同じSession IDで128日以上作業を継続できていました。

これは、Codexの長期作業機構が実際の開発運用でも機能していた事例です。

1.2 cached inputが長期セッションを支える

記録上、Input tokensの約97.16%はcached inputでした。

Input tokens:          11,373,888,419
Cached input tokens:   11,051,251,968
Output tokens:             33,928,455
Reasoning output:           7,176,913
Total tokens:          11,407,816,874

長期セッションでは、次のような共通情報が繰り返し参照されます。

  • リポジトリ構造
  • 開発ルール
  • 過去の設計判断
  • IssueとPRの関係
  • テストコマンド
  • CIとレビューの運用
  • 作業ディレクトリやworktreeの構成

cached inputの割合が高いことは、このような長い共通prefixや作業履歴が繰り返し利用されていたことと整合します。

1.3 Codexの「記憶」は3層で運用する

長期プロジェクトでは、Codexの記憶を一つの会話だけに閉じ込めないことが重要です。

今回の運用では、少なくとも次の3層が機能していました。

1. 現在の作業コンテキスト
   └─ モデルが現在参照する文脈。必要に応じてcompactionされる

2. ローカルのCodexセッション記録
   ├─ rollout JSONL
   ├─ state SQLite
   ├─ goal SQLite
   └─ logs SQLite

3. リポジトリとGitHub上の正本
   ├─ Git commit
   ├─ GitHub Issue / PR / review / CI
   ├─ AGENTS.md
   ├─ task ledger
   ├─ Runbook
   └─ テスト証跡

Codexの自動compactionは、会話の連続性を維持するうえで非常に有効です。

同時に、設計判断、完了条件、停止条件、再開手順をGitHubやtask ledgerへ外部化しておくことで、端末停止やプロセス再起動後も確実に作業を再開できます。

1.4 CLIの更新をまたいで同じセッションを継続できた

このセッションはCodex CLI 0.118.0で開始し、後半では0.147.0を使用していました。

プロセスを終了しても、同じSession IDをresumeすることで作業を継続できています。

つまり、長期継続の単位は一つのLinuxプロセスではなく、ローカルに永続化されたCodexセッションです。

Codexプロセスを終了
      ↓
codex resume
      ↓
同じセッションの作業文脈を再構成
      ↓
Git / GitHub / task ledgerを再確認
      ↓
開発を継続

2. 一つのセッションで継続した作業

このセッションは、一つのIssueだけを扱っていたわけではありません。

数か月の間に、例えば次のような作業を継続しました。

  • アプリケーションとインフラ構成の再設計
  • Google DriveとS3互換ストレージの抽象化
  • バックアップ、復元、監視境界の設計
  • WSL2上のrootless Podmanによる分離検証
  • backend graceful shutdownの実装
  • Knowledge Hubのsnapshot、provenance、annotation、conversation import
  • Chat threadとreplyの基盤
  • KnowledgeからChatへの選択的な共有
  • threadからKnowledge synthesisへの昇格
  • 外部LLMのprovider境界、予算、reservation、reconciliation
  • PWA Share TargetとManifest V3ブラウザ拡張
  • E2E flaky testの原因分析と修正
  • PRレビュー、CI監視、冷却期間、マージ後確認

この間、Codexは一つの巨大な作業を一度に実行したのではなく、IssueとPRを作業単位として分割しながら進めています。

各taskの終了時には、次のような情報を残しました。

  • 完了したIssueとPR
  • source headとmerge commit
  • 実行したテスト
  • 未実施範囲
  • 残存リスク
  • rollback
  • 次の再開条件

長期セッションにおけるCodexの強みは、過去の設計判断を参照しながら、次の作業単位へ継続的に移れることです。

2.1 このセッションで実際に完了した代表例

「838件のtask」という数字だけでは作業の中身が見えにくいため、同じ長期セッション内で完了した代表的な成果を抜粋します。いずれも、Issueの調査からPR分割、実装、テスト、レビュー、CI、冷却、マージ、cleanupまでを継続して処理したものです。

作業領域 実際の完了結果 主な検証結果
ストレージ/バックアップ基盤 Issue #1976#1980のrepo-side実装を完了し、Google Drive共通adapter、Sakura S3 profile、Drive secondary backup、統合readinessをマージ backend 1,472件、frontend 468件、focused coverage 87〜92%台、GitHub Actions failure 0
WSL2分離検証 PR #1974をマージ。専用Linuxユーザーとrootless Podmanで開発ユーザーから分離したprivate-smoke相当環境を構築 93 migrations、/healthz 200、/readyz 200、frontend 200、host publish 0、Caddy 0、backup 312,298 bytes
Knowledge provenance/import Issue #2013を3本のPRで完了。annotation、conversation import、synthesis provenanceを追加 backend 1,928件、frontend 564件、core E2E 106件、PostgreSQL 15 integration、old-application compatibility
Chat thread/reply Issue #2014を3本のPRで完了。thread、reply、notification、search、unread、ACK、UIを統合 backend 2,040件、frontend 719件、full E2E 153 PASS、cursor tamper 100/100
Knowledge→Chat共有/promote Issue #2015を4本のPRで完了。選択fieldだけのimmutable shareと、選択messageからsynthesisへの昇格を追加 backend 2,168件、frontend 793件、extended E2E 154 PASS、release-readiness 9分49秒

例えば、WSL2でのPodman検証では、次のような結果を残しています。

PostgreSQL readiness          PASS
Prisma migration              93 migrations / pending 0 / exit 0
backend /healthz              HTTP 200
backend /readyz               HTTP 200
frontend /                    HTTP 200
backend/frontend restart      PASS
DB restart後readiness         PASS
host publish                  0
Caddy                         0
backup size                   312,298 bytes
backup checksum               PASS

また、Knowledge HubとChatの機能追加では、単にコードを書くだけでなく、次の運用を一貫して繰り返しています。

Issueの固定契約を記録
  ↓
専用branch / worktree
  ↓
focused test / full test / PostgreSQL integration
  ↓
Draft PR
  ↓
独立review / Copilot / review completeness
  ↓
required CI
  ↓
exact-head固定後の冷却
  ↓
merge / main CI / cleanup

このように、長期セッションが保持していたのは単なる会話の続きではありません。過去の設計境界、テスト方針、レビュー運用、未実施範囲、rollback方針を再利用しながら、異なる機能領域を連続して進めていました。


3. 障害の発生

長期継続中のメインセッションで、主作業を止めずに横から状態確認を行う目的で/sideを実行しました。

/sideは、親スレッドを維持したまま一時的なside conversationを開始する機能です。side conversationには親スレッドの履歴が参照情報として引き継がれます。

この切替時に、TUIが応答しない状態になりました。

観測した状態は次のとおりです。

項目 内容
OS Windows 11 + WSL2 Ubuntu
Terminal Windows Terminal
Codex CLI 0.147.0
同時稼働 複数のCodex CLIセッション
親セッション 128日以上継続、ログ約1.33GB
操作 /sideによるside conversation切替
症状 TUIが応答せず、side切替が完了しない
native Codex RSS 約4.4GiB
WSL2 Ubuntu 正常に動作

WSL2自体は動作していたため、別の端末から同じUbuntuインスタンスへ入り、対象プロセスを調査しました。診断開始時の実際の環境出力は次のとおりです。

time=2026-08-15T14:42:45+09:00
observer_tty=/dev/pts/9
codex-cli 0.147.0
Linux 6.18.33.2-microsoft-standard-WSL2 x86_64

               total   used   free  buff/cache  available
Mem:            23Gi   6.5Gi  14Gi      2.8Gi       16Gi
Swap:          8.0Gi   2.7Gi 5.3Gi

WSL2全体には約16GiBのavailable memoryがあり、Ubuntuやシステム全体のOOMではありませんでした。問題は特定のCodexプロセスに局在していました。

公開されているCodexのIssueにも、長時間セッションで/sideを実行した際にSide starting...のままTUIが停止する類似報告があります。

今回の観測だけでは内部原因までは確定できません。ただし、通常のresumeと通常ターンは安定し、問題が長大な親履歴に対する/side切替時に集中したことから、親履歴のforkまたは再構成時にメモリ負荷が増幅した可能性が考えられます。


4. 複数のCodexから対象セッションを特定する

複数のCodex CLIを並行稼働させている環境では、対象を確定する前にpkill codexを実行してはいけません。

他の作業中セッションまで終了してしまいます。

4.1 Codex関連プロセスを一覧表示する

別のWSL2シェルを開き、次を実行します。

ps -eo \
  user:16,pid,ppid,pgid,sid,tty,stat,etime,pcpu,pmem,rss,wchan:28,args \
  | grep -E '[c]odex|[a]pp-server'

npm経由でインストールしたCodex CLIは、概ね次のプロセス構造になります。

node .../bin/codex
└─ .../vendor/.../bin/codex
   └─ codex-code-mode-host
プロセス 役割
node .../bin/codex npmの起動wrapper
.../vendor/.../bin/codex native Codex本体
codex-code-mode-host 補助プロセス

診断やシグナル送信の対象は、通常はnative Codex本体です。

4.2 TTY、Git branch、HEAD、CWDを一覧化する

次のスクリプトで、各native CodexがどのTTYとGitディレクトリに属するかを一覧化できます。

{
  printf \
    'TTY\tNATIVE\tWRAPPER\tPGID\tSID\tELAPSED\tCPU\tRSS_MiB\tBRANCH\tHEAD\tCWD\n'

  for pid in $(pgrep -x codex | sort -n); do
    tty="$(ps -o tty= -p "$pid" | xargs)"
    wrapper="$(ps -o ppid= -p "$pid" | xargs)"
    pgid="$(ps -o pgid= -p "$pid" | xargs)"
    sid="$(ps -o sid= -p "$pid" | xargs)"
    elapsed="$(ps -o etime= -p "$pid" | xargs)"
    cpu="$(ps -o pcpu= -p "$pid" | xargs)"
    rss_kib="$(ps -o rss= -p "$pid" | xargs)"
    rss_mib="$((rss_kib / 1024))"

    cwd="$(readlink -f "/proc/$pid/cwd" 2>/dev/null || printf '?')"

    branch="$(
      git -C "$cwd" symbolic-ref --quiet --short HEAD 2>/dev/null ||
        printf '-'
    )"

    head="$(
      git -C "$cwd" rev-parse --short=12 HEAD 2>/dev/null ||
        printf '-'
    )"

    printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
      "$tty" \
      "$pid" \
      "$wrapper" \
      "$pgid" \
      "$sid" \
      "$elapsed" \
      "$cpu" \
      "$rss_mib" \
      "$branch" \
      "$head" \
      "$cwd"
  done
} | column -t -s $'\t'

実際の実行結果では、複数のCodexの中から対象を次のように絞り込みました。出力は対象判断に必要な列と行へ抜粋しています。

実際のTTY/PID/RSS/branch一覧(抜粋)
TTY     NATIVE   WRAPPER  PGID     SID      ELAPSED      CPU  RSS_MiB  BRANCH                              HEAD          CWD
pts/7   1046456  1046432  1046432  1046195  15-03:59:06  0.1  138      <other-session>                     <omitted>     <other-worktree>
pts/6   2365521  2365497  2365497  12255    6-10:12:03   0.8  418      <other-session>                     <omitted>     <other-worktree>
pts/10  3743766  3743743  3743743  3743468  2-05:38:09   4.6  4379     security/codex-goal-audit-20260514  981a5fa88c43  .../ITDO_ERP4

pts/10だけが約4.3GiBを使用しており、対象プロジェクトの起動元ディレクトリにも一致しました。これでnative Codex PIDを3743766に絞れました。

対象セッションは、次の情報を組み合わせて特定します。

  • TTY
  • native PID
  • branch
  • HEAD
  • CWD
  • GitHub PRのhead SHA
  • task ledgerに記録されたworktree

4.3 /proc/PID/cwdだけに依存しない

Codexをメインcheckoutから起動し、実際の作業では別のGit worktreeを使っている場合があります。

その場合、/proc/PID/cwdは起動元を指したままです。

git worktree list

gh pr view <PR番号> \
  --json headRefName,headRefOid,baseRefOid,state,isDraft

今回も、この確認が必要でした。/proc/3743766/cwdから見えたbranchは次でした。

branch: security/codex-goal-audit-20260514
HEAD:   981a5fa88c43
CWD:    /home/devuser/work/CodeX/ITDO_ERP4

一方、実際に進めていたPRのtask worktreeは次でした。

/home/devuser/work/CodeX/ITDO_ERP4/worktrees/2017-browser-capture-extension

Codex自体は正本checkoutから起動し、ツール実行ではtask worktreeを明示していたためです。TTY、CWD、branchのいずれか一つだけで断定せず、PR headやtask ledgerと照合します。

4.4 Windows Terminalのタブを特定する

対象TTYのsession leaderからWT_SESSIONを確認できます。

SID=<session-id>

tr '\0' '\n' <"/proc/$SID/environ" 2>/dev/null \
  | grep -E '^(WT_SESSION|WT_PROFILE_ID|TERM_PROGRAM|TERM|TMUX)='

各タブで次を実行して比較します。

printf 'tty=%s\nWT_SESSION=%s\n' \
  "$(tty)" \
  "${WT_SESSION:-unset}"

対象TTYのタブタイトルだけを一時変更する方法もあります。

printf '\033]0;HUNG-CODEX pts/10\007' > /dev/pts/10

通常文字列をechoでTTYへ書き込むとTUI表示を崩す可能性があるため、端末タイトルの制御シーケンスだけを使います。


5. 本当にハングしているかを観測する

対象TTYを特定したら、プロセスツリーを確認します。

TARGET_TTY=pts/10

ps -t "$TARGET_TTY" \
  -o pid,ppid,pgid,tpgid,sid,tty,stat,etime,pcpu,pmem,rss,nlwp,wchan:24,args \
  --forest

実際の出力は次の構造でした。

PID      PPID     PGID     TPGID    SID      TTY     STAT  ELAPSED      RSS KiB
3743468  3743467  3743468  3743743  3743468  pts/10  Ss    2-05:41:14     5,404   -bash
3743743  3743468  3743743  3743743  3743468  pts/10  Sl+   2-05:40:34    35,788   node .../codex
3743766  3743743  3743743  3743743  3743468  pts/10  Sl+   2-05:40:34 4,484,672   .../bin/codex resume
3779188  3743766  3779188  3743743  3743468  pts/10  Sl    2-00:52:05    11,448   codex-code-mode-host

native Codex本体はPID 3743766で、RSSは4,484,672 KiB、約4.28GiBでした。Node.js wrapperや補助hostではなく、このPIDを観測対象にしました。

今回のnative CodexはSl+でした。

文字 意味
S interruptible sleep
l マルチスレッド
+ foreground process group

futex_do_waitと表示されていても、それだけでdeadlockとは判断できません。Rust/Tokioのworkerが通常待機している場合にも表示されます。

5.1 CPU、I/O、RSSを時系列で確認する

TARGET=<native-codex-pid>

for i in 1 2 3 4 5 6; do
  echo "===== $(date -Is) ====="

  ps -o \
    pid,stat,etime,pcpu,pmem,rss,nlwp,wchan:28,args \
    -p "$TARGET"

  grep -E \
    '^(rchar|wchar|read_bytes|write_bytes):' \
    "/proc/$TARGET/io" 2>/dev/null || true

  echo
  sleep 5
done

判断の目安は次のとおりです。

状態 解釈
CPU、I/O、RSSが変化する 何らかの処理中
CPU 0%、I/O不変 待機または停止の可能性
RSSが単調に増加 メモリ増幅やループの可能性
STAT=D カーネルI/O待ち
子プロセスが動作中 親を止める前に処理内容を確認

実際の30秒観測では、次のようにRSSは約4.28GiBで固定され、rcharwcharだけが少量進みました。

2026-08-15T16:38:01+09:00
PID      STAT  %CPU  RSS KiB   Threads  WCHAN
3743766  Sl+   4.5   4484672   24       futex_do_wait
rchar:       1619541580082
wchar:        152246370939
read_bytes:   110284820736
write_bytes:  163339165696

2026-08-15T16:38:26+09:00
PID      STAT  %CPU  RSS KiB   Threads  WCHAN
3743766  Sl+   4.5   4484672   24       futex_do_wait
rchar:       1619545990667
wchar:        152246479947
read_bytes:   110284820736
write_bytes:  163339276288

完全に停止しているわけではありませんが、画面上の/side切替は完了せず、RSSも高止まりしていました。そのため、hard deadlockというより、side conversation初期化または履歴forkに伴う処理が進まない状態と判断しました。


6. 破壊性の低い順に復旧する

6.1 SIGWINCHでTUIを再描画する

最初に試したのはSIGWINCHです。

kill -WINCH "$TARGET"

SIGWINCHはterminal size changeを通知するシグナルです。TUIが内部では動作しているものの表示が更新されていない場合、再描画の契機になります。

実際には次の順で実行しました。

TARGET=3743766
kill -CONT "$TARGET"
kill -WINCH "$TARGET"
sleep 3
ps -o pid,stat,etime,pcpu,pmem,rss,wchan:28,args -p "$TARGET"

直後のpsではプロセス自体はまだ存在していました。

PID      STAT  ELAPSED      %CPU  %MEM  RSS KiB  WCHAN
3743766  Sl+   2-05:48:20  4.5   18.2  4484672  futex_do_wait

しかし対象のWindows TerminalタブではTUIが再描画され、その後bashプロンプトへ戻りました。追加のSIGKILLやWSL再起動は不要でした。

SIGWINCHが直接終了させたとは断定できませんが、少なくともWSL全体を再起動せず、対象TTYの状態を復旧させるきっかけになりました。

6.2 必要な場合だけSIGINT

再描画しても操作できなければ、native Codexへ1回だけSIGINTを送ります。

kill -INT "$TARGET"
sleep 8

これは端末でのCtrl+Cに相当します。

1回で変化がない場合は、もう1回まで試します。

kill -INT "$TARGET"
sleep 8

連打は避けます。

6.3 process groupへ送る場合

PID単体で反応しない場合に限り、process groupを確認します。

PGID="$(ps -o pgid= -p "$TARGET" | xargs)"

ps -o pid,ppid,pgid,sid,tty,stat,args \
  --pgid "$PGID" \
  --forest

shellや別作業が含まれていないことを確認してから、process groupへ送ります。

kill -INT -- "-$PGID"

6.4 一括終了は避ける

pkill -f codex
killall codex

複数のCodexセッションが稼働している環境では、他の作業まで終了するため使用しません。


7. Codex終了前に作業状態を保存する

Codexプロセスを終了しても、commit済み・未commitのファイルはworktree上に残ります。

それでも、終了前にGitとGitHubの状態を確認しておくと、再開が確実になります。

WT=/path/to/task-worktree

 git -C "$WT" status --short --branch
 git -C "$WT" rev-parse HEAD
 git -C "$WT" rev-parse '@{upstream}'
 git -C "$WT" rev-list --left-right --count '@{upstream}...HEAD'

 git -C "$WT" diff --stat
 git -C "$WT" diff --cached --stat

理想的なcheckpointは次です。

worktree clean
local HEAD = upstream HEAD
local HEAD = PR head
ahead / behind = 0 / 0

今回、同じセッションをresumeした後にCodex自身へcheckpoint確認だけを依頼したところ、次の状態が確認できました。

branch:          feat/2017-browser-capture-extension
local HEAD:      54de9a408b55e748999806226d740b58f349505f
upstream HEAD:   54de9a408b55e748999806226d740b58f349505f
ahead / behind:  0 / 0
worktree:        clean
origin/main:     56232be5dfd7317551c3838265dc86e3939a9bc7

PR #2074:        OPEN / Draft / MERGEABLE / CLEAN
required checks: backend, frontend, lint, E2E, API schema, audit,
                 secret scan, CodeQL, Link Check すべてPASS
review threads:  0
review completeness: ok

つまり、TUI障害で失われたのは進行中の画面状態であり、コード、commit、push、PR、CI、レビュー記録はすべて残っていました。未追跡のテスト証跡がある場合は、削除せず別ディレクトリへコピーします。

長期セッションでは、会話の最後の回答よりも、Git commit、GitHub PR、CI、task ledgerを作業の正本として扱う方が復旧しやすくなります。


8. 同じセッションをresumeして安定性を確認する

対象Codexが終了してbashへ戻った後、同じSession IDをcodex resumeで再開しました。

codex resume

再開後のnative Codexを特定します。

TARGET_TTY=pts/10

NATIVE_PID="$(
  ps -t "$TARGET_TTY" -o pid=,comm=,args= |
    awk '$2 == "codex" && $0 !~ /codex-code-mode-host/ {
      print $1
      exit
    }'
)"

printf 'native_pid=%s\n' "$NATIVE_PID"

8.1 再開後の初期状態

再開後のnative CodexはPID 2049091でした。実際の/proc出力は次のとおりです。

Name:     codex
State:    S (sleeping)
VmPeak:   2894580 kB
VmSize:    947540 kB
VmHWM:    2093540 kB
VmRSS:     520616 kB
VmSwap:         0 kB
Threads:        24

Rss:       520616 kB
Pss:       456798 kB
Pss_Anon:  384540 kB
Pss_File:   72258 kB
Swap:           0 kB
fd_count:      34

MiBへ換算すると、概ね次の値です。

VmPeak:  約2.76GiB
VmHWM:   約2.00GiB
VmRSS:   約508MiB
PSS:     約446MiB
VmSwap:  0
Threads: 24
FD:      34〜35

VmHWMはプロセス開始後の最大RSSです。resume時に長いセッション履歴を読み込み、一時的に約2GiBまで増えた後、現在RSSは約508MiBまで下がっていました。

高いVmHWMが残っていても、現在のRSSとPSSが低下して安定していれば、それだけでメモリリークとは判断しません。

8.2 アイドル状態の測定

約7分半、10秒間隔で測定しました。

指標 開始 終了 変化
Native RSS 508.4MiB 508.4MiB 0
Native PSS 446.1MiB 446.1MiB 0
Process tree RSS 562.1MiB 562.1MiB 0
Threads 24 24 24〜25で安定
FD 35 35 34〜35で安定
Native swap 0 0 0
Process数 2 2 0

CSVの先頭と終端付近は次のとおりです。

timestamp,native_pid,state,native_rss_mib,native_pss_mib,native_hwm_mib,native_swap_mib,tree_rss_mib,threads,fds,tree_processes,rollout_mib,mem_available_mib,swap_free_mib
2026-08-15T17:24:42+09:00,2049091,Sl+,508.4,446.1,2044.5,0.0,562.1,24,35,2,1270.6,20735.3,5461.9
2026-08-15T17:24:53+09:00,2049091,Sl+,508.4,446.1,2044.5,0.0,562.1,24,34,2,1270.6,20624.9,5461.9
...
2026-08-15T17:32:38+09:00,2049091,Sl+,508.4,446.1,2044.5,0.0,562.1,24,35,2,1270.6,21076.7,5461.9

アイドル中のRSSとPSSは全測定点で同じ値でした。

8.3 通常のread-onlyターン1回目

/sideは使わず、GitとGitHubの状態を確認する軽量なread-onlyターンを実行しました。

処理中ピーク
RSS:      約514.4MiB
PSS:      約452.0MiB
Threads:  31〜32
FD:       44〜45

処理後の定常値
RSS:      約509.5MiB
PSS:      約447.2MiB
Threads:  28前後
FD:       44〜45

実際のCSVでは、処理開始付近に一時ピークが現れ、その後すぐに定常値へ戻っています。

2026-08-15T17:55:03+09:00,2049091,Sl+,507.7,445.4,2044.5,0.0,561.4,24,33,2,1270.6,21049.2,5461.9
2026-08-15T17:55:13+09:00,2049091,Sl+,514.4,452.0,2044.5,0.0,568.0,31,44,2,1270.6,21048.1,5461.9
2026-08-15T17:55:23+09:00,2049091,Sl+,514.4,452.0,2044.5,0.0,568.0,32,45,2,1270.6,21008.5,5461.9
2026-08-15T17:55:53+09:00,2049091,Sl+,509.7,447.3,2044.5,0.0,563.3,28,45,2,1270.6,21044.2,5461.9
2026-08-15T18:02:49+09:00,2049091,Sl+,509.5,447.2,2044.5,0.0,563.2,28,44,2,1270.6,21058.0,5461.9

処理中に約6MiB増えましたが、終了後は横ばいになりました。

8.4 通常のread-onlyターン2回目

同じ規模の処理をもう一度実行しました。

処理中ピーク
RSS:      約514.5MiB
PSS:      約450.1MiB
Threads:  34
FD:       48

処理後の定常値
RSS:      約509.6MiB
PSS:      約447.2MiB
Threads:  28前後
FD:       44〜45

2回目の処理後も定常値は1回目とほぼ同じでした。

アイドル:      RSS 508.4MiB
1回目処理後:   RSS 509.5MiB
2回目処理後:   RSS 509.6MiB

2回目のCSVも同じ傾向でした。

2026-08-15T18:15:13+09:00,2049091,Sl+,514.5,450.1,2044.5,0.0,568.1,34,48,2,1270.6,21014.4,5462.0
2026-08-15T18:15:23+09:00,2049091,Sl+,510.0,447.6,2044.5,0.0,563.6,34,45,2,1270.6,21042.3,5462.0
2026-08-15T18:15:33+09:00,2049091,Sl+,509.9,447.5,2044.5,0.0,563.6,28,45,2,1270.6,21039.5,5462.0
...
2026-08-15T18:27:12+09:00,2049091,Sl+,509.6,447.2,2044.5,0.0,563.2,28,45,2,1270.6,21054.6,5462.0

処理ごとに100MiB、200MiBと積み上がる階段状の増加はありませんでした。


9. ThreadsとFDの増加をどう読むか

1回目の通常ターン後、ThreadsとFDは次のように増えました。

Threads: 24 → 28
FD:      34〜35 → 44〜45

FD一覧の差分では、次のSQLiteファイルが新しく開かれていました。

+18 -> /home/devuser/.codex/state_5.sqlite
+19 -> /home/devuser/.codex/state_5.sqlite-wal
+20 -> /home/devuser/.codex/goals_1.sqlite
+21 -> /home/devuser/.codex/goals_1.sqlite-wal
+23 -> /home/devuser/.codex/goals_1.sqlite-shm
+25 -> /home/devuser/.codex/goals_1.sqlite
+27 -> /home/devuser/.codex/state_5.sqlite
+28 -> /home/devuser/.codex/goals_1.sqlite-wal
+29 -> /home/devuser/.codex/state_5.sqlite-wal
+30 -> /home/devuser/.codex/state_5.sqlite-shm

thread差分では、sqlx-sqlite-workerが一時的に複数起動していました。

-2049091 2086723 ... sqlx-sqlite-worker
+2049091 2110110 ... sqlx-sqlite-worker
+2049091 2110114 ... sqlx-sqlite-worker
+2049091 2110115 ... sqlx-sqlite-worker
+2049091 2110116 ... sqlx-sqlite-worker
+2049091 2125981 ... sqlx-sqlite-worker

Threadsにはsqlx-sqlite-workerが追加されていました。

2回目の通常ターン後もThreadsとFDは同じ範囲で安定し、さらに同じ量が追加されることはありませんでした。

1回目後: Threads 28、FD 44〜45
2回目後: Threads 28、FD 44〜45

一時的にはThreads 34、FD 48まで増えましたが、処理後に減少しています。

今回の範囲では、これはstate、goal、log用SQLite接続の遅延初期化と接続プールの通常動作と考えられます。


10. メモリ監視スクリプト

以下は、native Codexとprocess tree全体のRSS、PSS、Threads、FD、rolloutサイズをCSVへ保存するスクリプトです。

watch-codex-memory.sh
#!/usr/bin/env bash
set -euo pipefail

target_tty="${1:?usage: watch-codex-memory.sh pts/N [interval-seconds] [output.csv]}"
interval="${2:-10}"
output="${3:-$HOME/codex-memory-$(date +%Y%m%d-%H%M%S).csv}"

native_pid="$(
  ps -t "$target_tty" -o pid=,comm=,args= |
    awk '$2 == "codex" && $0 !~ /codex-code-mode-host/ {
      print $1
      exit
    }'
)"

if [[ -z "$native_pid" ]] || ! kill -0 "$native_pid" 2>/dev/null; then
  echo "native Codex process not found on $target_tty" >&2
  exit 1
fi

wrapper_pid="$(ps -o ppid= -p "$native_pid" | xargs)"

collect_tree() {
  local root="$1"
  local current
  local children_text
  local -a queue=("$root")
  local -a children=()

  while ((${#queue[@]} > 0)); do
    current="${queue[0]}"
    queue=("${queue[@]:1}")

    [[ -r "/proc/$current/status" ]] || continue
    printf '%s\n' "$current"

    children_text="$(
      cat "/proc/$current/task/$current/children" 2>/dev/null || true
    )"

    if [[ -n "$children_text" ]]; then
      read -r -a children <<<"$children_text"
      queue+=("${children[@]}")
    fi
  done
}

status_kb() {
  local field="$1"

  awk -v field="$field" '
    $1 == field ":" {
      print $2
      found=1
      exit
    }
    END {
      if (!found) print 0
    }
  ' "/proc/$native_pid/status" 2>/dev/null
}

to_mib() {
  awk -v kib="${1:-0}" 'BEGIN { printf "%.1f", kib / 1024 }'
}

rollout_path() {
  local fd
  local path

  for fd in "/proc/$native_pid/fd/"*; do
    path="$(readlink "$fd" 2>/dev/null || true)"

    case "$path" in
      */rollout-*.jsonl)
        printf '%s\n' "$path"
        return
        ;;
    esac
  done
}

printf '%s\n' \
  'timestamp,native_pid,state,native_rss_mib,native_pss_mib,native_hwm_mib,native_swap_mib,tree_rss_mib,threads,fds,tree_processes,rollout_mib,mem_available_mib,swap_free_mib' \
  >"$output"

while kill -0 "$native_pid" 2>/dev/null; do
  timestamp="$(date -Is)"
  state="$(ps -o stat= -p "$native_pid" | xargs || true)"

  rss_kib="$(status_kb VmRSS)"
  hwm_kib="$(status_kb VmHWM)"
  swap_kib="$(status_kb VmSwap)"
  threads="$(
    awk '/^Threads:/ { print $2 }' \
      "/proc/$native_pid/status" 2>/dev/null || echo 0
  )"

  pss_kib="$(
    awk '
      $1 == "Pss:" {
        print $2
        found=1
        exit
      }
      END {
        if (!found) print 0
      }
    ' "/proc/$native_pid/smaps_rollup" 2>/dev/null || echo 0
  )"

  fds="$(
    find "/proc/$native_pid/fd" \
      -mindepth 1 -maxdepth 1 2>/dev/null |
      wc -l
  )"

  mapfile -t tree_pids < <(collect_tree "$wrapper_pid")

  tree_rss_kib=0
  for pid in "${tree_pids[@]}"; do
    child_rss="$(
      awk '/^VmRSS:/ { print $2; exit }' \
        "/proc/$pid/status" 2>/dev/null || echo 0
    )"
    tree_rss_kib=$((tree_rss_kib + ${child_rss:-0}))
  done

  rollout="$(rollout_path || true)"
  rollout_bytes=0

  if [[ -n "$rollout" && -f "$rollout" ]]; then
    rollout_bytes="$(stat -c '%s' "$rollout" 2>/dev/null || echo 0)"
  fi

  mem_available_kib="$(
    awk '/^MemAvailable:/ { print $2 }' /proc/meminfo
  )"

  swap_free_kib="$(
    awk '/^SwapFree:/ { print $2 }' /proc/meminfo
  )"

  printf '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%.1f,%s,%s\n' \
    "$timestamp" \
    "$native_pid" \
    "$state" \
    "$(to_mib "$rss_kib")" \
    "$(to_mib "$pss_kib")" \
    "$(to_mib "$hwm_kib")" \
    "$(to_mib "$swap_kib")" \
    "$(to_mib "$tree_rss_kib")" \
    "$threads" \
    "$fds" \
    "${#tree_pids[@]}" \
    "$(awk -v bytes="$rollout_bytes" 'BEGIN { print bytes / 1048576 }')" \
    "$(to_mib "$mem_available_kib")" \
    "$(to_mib "$swap_free_kib")" \
    >>"$output"

  tail -n 1 "$output"
  sleep "$interval"
done

実行例です。

chmod 700 watch-codex-memory.sh

timeout 1800s \
  ./watch-codex-memory.sh \
  pts/10 \
  10 \
  "$HOME/codex-memory.csv"

11. 今回の測定結果

実測値を一つの表にまとめると次のとおりです。

フェーズ Native RSS Native PSS Threads FD Swap
/side障害時 約4,379MiB 未取得 24〜25 未取得 未取得
resume後アイドル 508.4MiB 446.1MiB 24〜25 34〜35 0
通常ターン1回目ピーク 514.4MiB 452.0MiB 32 45 0
通常ターン1回目定常 509.5MiB 447.2MiB 28 44〜45 0
通常ターン2回目ピーク 514.5MiB 450.1MiB 34 48 0
通常ターン2回目定常 509.6MiB 447.2MiB 28 44〜45 0

今回の観測をまとめると次のようになります。

同じセッションのresume              成功
アイドル状態                         安定
通常read-onlyターン1回目             安定
通常read-onlyターン2回目             安定
ターンごとのRSS/PSS累積              なし
process増殖                          なし
Threads/FDの階段状増加               なし
native Codexのswap                   なし
長大な親履歴からの /side             TUI停止と大幅なRSS増加を観測

通常のresumeと通常ターンでは、一般的なメモリリークは確認できませんでした。

障害時に約4.4GiBだったRSSは、同じセッションを再開した後には約508MiBで安定しました。通常ターンを2回実行しても、処理後の定常値は約510MiBのままでした。

この結果から、今回の問題はCodexセッション全体の長期継続ではなく、GB級に成長した親セッションを/sideでforkする局面に集中した可能性が高いと考えています。


12. 長期Codexセッションを安定して運用する方法

12.1 長期セッションは実用になる

今回のセッションは、128日以上、838件の完了task、累積11.4B tokensという規模まで継続できました。

自動compaction、cached input、ローカルセッション永続化、Git/GitHubへの作業状態の外部化を組み合わせることで、Codexは長期プロジェクトの開発エージェントとして運用できます。

12.2 GB級のセッションでは/sideを避ける

通常のresumeと通常ターンは安定していましたが、/side切替時には大幅なRSS増加とTUI停止が発生しました。

ログがGB級まで成長したセッションで横道の調査を行う場合は、/sideではなく別の短いCodexセッションを起動する方が安全です。

メインの長期セッション
   └─ 実装と長期文脈の保持

別の短いセッション
   └─ read-only監査、GitHub状態確認、設計レビュー

12.3 GitHubとtask ledgerを正本にする

長期セッションを安全に終了・再開できる条件は、会話履歴だけではありません。

常に次を記録します。

  • Issue
  • PR
  • branch
  • exact head
  • merge commit
  • CI
  • review
  • 未解決thread
  • 未実施範囲
  • rollback
  • 再開条件
  • task ledger

これにより、Codexプロセスを終了しても、新しいセッションが作業状態を再構築できます。

12.4 task単位でcheckpointを作る

長時間自走では、次の区切りでcheckpointを残します。

  • focused test完了
  • full test完了
  • commit
  • push
  • Draft PR
  • review対応
  • exact-head CI
  • cooling開始
  • merge
  • cleanup

「会話の途中にしか存在しない作業状態」を減らすことが、長期運用の安定性につながります。

12.5 セッション記録のサイズを監視する

du -sh ~/.codex
du -sh ~/.codex/sessions
du -sh ~/.codex/*.sqlite* 2>/dev/null

今回のようにJSONLがGB級へ成長した場合は、通常利用を続けられていても、fork、resume picker、履歴再構成など特定操作の負荷が高くなる可能性があります。

12.6 TUIログを有効にする

新しいセッションでは、最初からログディレクトリを指定できます。

codex -c log_dir=./.codex-log

別端末から監視します。

tail -F ./.codex-log/codex-tui.log

ログにはローカルパスや作業情報が含まれる可能性があるため、外部共有前に内容を確認します。


13. 障害時の短縮チェックリスト

対象の特定

ps -eo pid,ppid,pgid,sid,tty,stat,etime,pcpu,pmem,rss,args \
  | grep -E '[c]odex'
  • TTY
  • native PID
  • branch
  • HEAD
  • worktree
  • PR head

を照合します。

観測

ps -o pid,stat,etime,pcpu,pmem,rss,nlwp,wchan:28,args -p "$TARGET"
cat "/proc/$TARGET/io"

復旧

kill -WINCH "$TARGET"

必要な場合だけ次へ進みます。

kill -INT "$TARGET"

作業状態の確認

git -C "$WT" status --short --branch
git -C "$WT" rev-parse HEAD
git -C "$WT" rev-parse '@{upstream}'

再開

codex resume

再開後はRSS、PSS、Threads、FDを時系列で確認します。


まとめ

このセッションは、128日以上にわたり、838件の完了task、累積11.4B tokens、約1.33GBのローカル記録という規模まで継続しました。

Codexは自動compactionとセッション永続化によって、長期プロジェクトの文脈を保ちながら作業を続けられます。Git、GitHub、task ledgerを外部記憶として組み合わせることで、IssueからIssueへ、PRからPRへと開発を継続できました。

今回の/side障害は、この長期継続能力そのものが破綻した事例ではありません。

同じセッションをresumeした後は、RSS約508MiBで安定し、通常のread-onlyターンを2回実行しても定常値の累積増加はありませんでした。一般的なメモリリークも確認されていません。

今回の復旧では、実際に次の順序で操作し、WSL2全体や他のCodexセッションを止めずに対象だけを復旧できました。

複数CodexのTTY/PID/RSS一覧を取得
  ↓
pts/10、native PID 3743766を特定
  ↓
process treeとI/Oを30秒観測
  ↓
SIGWINCHでTUI再描画
  ↓
bashプロンプトへ復帰
  ↓
codex resume
  ↓
Git/PRのcheckpoint一致を確認
  ↓
アイドル+通常ターン2回のメモリ測定

今回得られた主な知見は次のとおりです。

  1. Codexの単一セッションは数か月にわたる開発を継続できる
  2. 自動compactionとcached inputが長期作業を支える
  3. GitHubとtask ledgerを正本にするとプロセス障害から復旧しやすい
  4. 複数セッション環境ではTTYとnative PIDを特定してから操作する
  5. TUI停止時は、すぐkillせずSIGWINCHによる再描画を試す
  6. 復旧後はRSSだけでなくPSS、Threads、FDを時系列で測る
  7. GB級へ成長した長期セッションでは、/sideより別セッションでのread-only監査が安全

Codexの長期作業能力は、実際の開発プロジェクトで十分に利用できます。

そのうえで、セッションが非常に大きくなったときには、通常の継続作業と履歴forkを伴う操作を分けて運用することが重要です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?