اگر پس از ارتقای کدکس تاریخ نشست‌های قدیمی‌تان ناپدید شده یا codex doctor هشدار rollout files exist but the state DB is missing می‌دهد، فرمان codex migrate-rollouts همان چیزی است که لازم دارید. در یک آزمایش اندازه‌گیری‌شده روی کدکس 0.158.0، یک نشست قدیمی سه‌سطری از 693 به 745 بایت رسید و همان نشست در دیتابیس SQLite ثبت شد.

علامت‌ها: تاریخ نشست کجاست

کدکس تاریخ هر نشست را در فایل‌های JSONL با نام rollout-*.jsonl زیر مسیر ~/.codex/sessions/YYYY/MM/DD/ نگه می‌دارد، نه در یک فایل واحد. یک پایگاه داده‌ی SQLite به نام state_5.sqlite هم هست که فهرست نشست‌ها را نمایه می‌کند. نقش این دو در نسخه‌های اخیر عوض شده است و همین باعث بیشتر گزارش‌های خرابی می‌شود.

در کدکس 0.158.0، برای یک خانه‌ی کدکس که فقط یک فایل نشست قدیمی داشت، فرمان codex doctor --summary این را چاپ کرد. تاریخ نشست‌ها همان‌طور که مستندات Codex CLI در بخش codex resume توضیح می‌دهند، از فایل‌های محلی خوانده می‌شود [6]:

# پیش از هر کاری وضعیت را بخوان: سطر threads تعیین می‌کند مهاجرت لازم است یا نه
$ codex doctor --summary --no-color | grep -E 'threads|state'
   ⚠ threads      rollout files exist but the state DB is missing
  ✓ state        state paths and databases are inspectable
17 ok · 1 idle · 4 notes · 2 warn · 1 fail failed

همان فرمان پس از اجرای مهاجرت، هشدار را به تیک تبدیل کرد. عدد 1 fail در هر دو حالت ماند چون آن یکی به ورود به حساب مربوط بود، نه به تاریخ نشست.

$ codex doctor --summary --no-color | grep -E 'threads|state|ok ·'
  ✓ state        databases healthy
  ✓ threads      rollout files and state DB thread inventory agree
18 ok · 1 idle · 3 notes · 1 warn · 1 fail failed

اگر codex doctor را روی ماشینی بدون ورود به حساب اجرا کنید، همین یک شکست را می‌بینید. تفسیر درست از تفاوت دو سطر state و threads می‌آید، نه از عدد آخر.

مهاجرت دقیقا چه می‌کند

پیش از مهاجرت، سطر نخست فایل نشست من این بود:

{"timestamp":"2026-09-01T00:00:00.000Z","ordinal":0,"type":"session_meta","payload":{"session_id":"aaaa1111-…","id":"aaaa1111-…","timestamp":"2026-09-01T00:00:00.000Z","cwd":"/srv/app","originator":"codex_exec","cli_version":"0.150.0","source":"exec","thread_source":"user","model_provider":"openai"}}

پس از مهاجرت، دو کلید به همان شیء payload اضافه شد و بقیه‌ی سطر دست‌نخورده ماند:

…"model_provider":"openai","base_instructions":null,"history_mode":"paginated"}}

اندازه‌ی فایل از 693 به 745 بایت رسید و تعداد سطرها هر سه سطر ماند. یعنی مهاجرت محتوای گفتگو را بازنویسی نمی‌کند؛ فقط یک سطر متا را کامل می‌کند و یک رکورد نمایه می‌سازد. همان رکورد در مخزن کدکس نگه‌داری می‌شود [8].

بخش اصلی کار جای دیگری است: دیتابیس thread_history_1.sqlite ساخته می‌شود و جدول thread_history_projection_state در آن قرار می‌گیرد. در آزمایش من، next_rollout_byte_offset برابر 745 و next_rollout_ordinal برابر 3 شد، یعنی دقیقا پس از آخرین سطر فایل.

پیام پایانی خود کدکس عدد را گمراه‌کننده نشان می‌دهد. برای همان نشست سه‌سطری، خط زیر را دیدم:

Disk used for thread storage: 693 B -> 274.1 KB

این 274.1 KB ربطی به حجم گفتگوی شما ندارد. این اندازه‌ی چند دیتابیس خالی SQLite است که در نخستین اجرا ساخته می‌شوند، به‌علاوه‌ی فایل‌های -shm و -wal کنارشان. اندازه‌ی خود فایل JSONL تنها 52 بایت بزرگ‌تر شد، چون 745 - 693 = 52. برای دیدن اثر واقعی، خود پوشه را اندازه بگیرید:

# اثر واقعی را روی خود فایل ببینید، نه روی عدد پایانی خود کدکس
$ du -sb "$CODEX_HOME/sessions"
745	/root/.hermes/cache/scratch/migtruth/home/sessions

اجرای مهاجرت، از خشک تا واقعی

فرمان بدون هیچ پرچمی فقط گزارش می‌دهد و هیچ فایلی را تغییر نمی‌دهد. این را می‌توانید پیش از هر کاری اجرا کنید:

# بدون هیچ پرچمی: فقط گزارش. این اجرا هیچ فایلی را تغییر نمی‌دهد
$ codex migrate-rollouts
Scanning local rollouts...
Checking rollouts  1/1 (100%)  •  1 eligible  •  0 already paginated  •  0 skipped  •  0 failed  •  0s
Scan complete in 0s.
Scanned 1 rollout(s): 1 eligible, 0 already paginated, 0 skipped (0 empty, 0 busy), 0 failed.
Run `codex migrate-rollouts --apply` to migrate eligible sessions.

برای دیدن وضعیت هر نشست به صورت قابل پردازش، پرچم --json خروجی ماشینی می‌دهد که برای اسکریپت‌های مانیتورینگ لازم است. فهرست کامل پرچم‌ها در مرجع خط فرمان آمده است [7]:

# نمای ماشینی برای اسکریپت: هر نشست یک وضعیت دارد، نه فقط یک عدد
$ codex migrate-rollouts --json | grep -E '"thread_id"|"status"'
      "thread_id": "22222222-2222-2222-2222-222222222222",
      "status": "eligible",
      "thread_id": "33333333-3333-3333-3333-333333333333",
      "status": "skipped_empty",
      "thread_id": "11111111-1111-1111-1111-111111111111",
      "status": "eligible",

حالت skipped_empty برای فایل صفر بایتی است. در آزمایشی که سه فایل داشتم، خط پایانی دقیقا همین تفکیک را نشان داد: 2 eligible, 0 already paginated, 1 skipped (1 empty, 0 busy), 0 failed.

حالا اجرای واقعی. در نخستین اجرا همان نشست سه‌سطری مهاجرت کرد:

# اعمال واقعی: تنها جایی که فایل‌ها تغییر می‌کنند
$ codex migrate-rollouts --apply
Scanning local rollouts...
Migrating rollouts  1/1 (100%)  •  1 migrated  •  0 already paginated  •  0 skipped  •  0 failed  •  0s
Migration complete in 0s.
Scanned 1 rollout(s): 1 migrated, 0 already paginated, 0 skipped (0 empty, 0 busy), 0 failed.
Disk used for thread storage: 693 B -> 274.1 KB

اجرای دوم همان فرمان هیچ کاری نکرد و نشان داد که فرمان تکرارپذیر است. یعنی می‌توانید با خیال راحت در کران‌جاب اجرا کنید:

# اجرای دوم همان فرمان: تکرارپذیر است و دوباره کاری نمی‌کند
$ codex migrate-rollouts --apply
Migrating rollouts  1/1 (100%)  •  0 migrated  •  1 already paginated  •  0 skipped  •  0 failed  •  0s
Scanned 1 rollout(s): 0 migrated, 1 already paginated, 0 skipped (0 empty, 0 busy), 0 failed.
Disk used for thread storage: 88.7 KB -> 88.7 KB

اگر چند نشست قدیمی دارید و می‌خواهید یکی‌یکی جلو بروید، پرچم --thread همان کار را محدود می‌کند. در همان آزمایش سه‌فایلی، مهاجرت محدود فقط یک نشست را علامت‌گذاری کرد و بقیه دست‌نخورده ماندند:

# مهاجرت یک نشست مشخص: بقیه‌ی فایل‌ها دست‌نخورده می‌مانند
$ codex migrate-rollouts --apply --thread 11111111-1111-1111-1111-111111111111
Migrating rollouts  3/3 (100%)  •  1 migrated  •  0 already paginated  •  0 skipped  •  0 failed  •  0s
Migration complete in 0s.
Scanned 1 rollout(s): 1 migrated, 0 already paginated, 0 skipped (0 empty, 0 busy), 0 failed.
Disk used for thread storage: 1.0 KB -> 274.5 KB

$ codex migrate-rollouts --json | grep -E '"thread_id"|"status"'
      "thread_id": "22222222-2222-2222-2222-222222222222",
      "status": "eligible",
      "thread_id": "11111111-1111-1111-1111-111111111111",
      "status": "already_paginated",

مرزهایی که در آزمایش دیدم

سه محدودیت را مستقیم اندازه گرفتم و هر سه در کدکس 0.158.0 باز هستند. هر کدام به یک شماره‌ی باز در مخزن openai/codex وصل است [9].

وضعیتشمارهتاریخآنچه دیده شد
نام نشست پس از مهاجرت پاک می‌شود#38761۱۵ اوت ۲۰۲۶ستون name در state_5.sqlite مقدار None می‌ماند
فایل نشست بازیابی‌شده پذیرفته نمی‌شود#41814۳۱ اوت ۲۰۲۶--apply با نبود فراداده‌ی SQLite روبه‌رو می‌شود و کاری نمی‌کند
پشتیبان نشست پذیرفته نمی‌شود#40426۲۴ اوت ۲۰۲۶فایل JSONL بدون رکورد SQLite به‌تنهایی کافی نیست

شماره‌ی #41814 را جدی بگیرید اگر تاریخ نشست‌تان را از یک کپی پشتیبان برگردانده‌اید [4]. نشانه‌ی آن skipped_empty روی فایل‌هایی است که خالی نیستند، یعنی فراداده‌ی لازم در SQLite نیست. شماره‌ی #40426 همین مسیر را برای فایل‌های پشتیبان‌گیری‌شده ثبت کرده است [5].

نکته‌ی دوم این است که این مهاجرت هنوز خودکار نیست. در codex features list نسخه‌ی 0.158.0 پرچم background_paginated_rollout_migration با وضعیت under development و مقدار false آمد. یعنی نسخه‌ی پس‌زمینه‌ای هنوز روشن نیست و شما باید فرمان را دستی بزنید. انتشار 0.158.0 هم طبقه‌بندی خطاهای خواندن rollout را اضافه کرد که همان چیزی است که به شما اجازه می‌دهد خرابی را از «نشست گم شده» تفکیک کنید [1].

پس‌زمینه‌ای که این فرمان روی آن سوار است، صفحه‌بندی تاریخ نشست است. در انتشار 0.159.0 در ۲۹ سپتامبر ۲۰۲۶ [2]، کمپین #48151 قابلیت صفحه‌بندی تاریخ از یک آیتم مشخص را به کلاینت‌های app-server اضافه کرد [3]. اگر از کلاینت خودتان تاریخ نشست را می‌خوانید، همین PR رفتار خواندن را تغییر می‌دهد.

برای اینکه بدانید پرچم‌های نسخه‌ی خودتان با این فهرست یکی هستند یا نه، از همان فرمانی که در پست خواندن وضعیت پرچم‌ها توضیح داده شد استفاده کنید. در نسخه‌ی من ۱۵۱ پرچم چاپ شد که 47 تای آن stable بود و 40 تای دیگر removed. این عدد با هر انتشار جابه‌جا می‌شود، پس آن را عدد ثابت نخوانید.

ترتیب پیشنهادی اجرا

اگر تاریخ نشست‌های شما مشکل دارد، این ترتیب را بروید. گام اول را حذف نکنید، چون تنها گامی است که به شما اجازه می‌دهد پیش از هر تغییری ببینید چه چیزی در خطر است.

  1. از پوشه‌ی ~/.codex یک کپی بگیرید. این تنها گامی است که پشتیبان‌گیری می‌خواهد.
  2. فرمان codex doctor --summary --no-color را اجرا کنید و سطر threads را بخوانید. اگر تیک خورد، مهاجرت لازم نیست.
  3. فرمان codex migrate-rollouts را بدون --apply اجرا کنید و خط Scanned را با تعداد نشست‌های واقعی‌تان مقایسه کنید.
  4. فرمان codex migrate-rollouts --apply را اجرا کنید و در خط پایانی دنبال 0 failed بگردید.
  5. فرمان codex doctor --summary --no-color را دوباره اجرا کنید. باید rollout files and state DB thread inventory agree را ببینید.
  6. فرمان codex migrate-rollouts --json را اجرا کنید و دنبال وضعیت skipped_empty روی فایل‌های غیرخالی بگردید. اگر بود، مشکل شما با این فرمان حل نمی‌شود.

قاعده‌ی کاربردی این آزمایش در یک جمله جمع می‌شود: codex migrate-rollouts فرمانی بی‌خطر و تکرارپذیر است که تنها دو کلید به سطر session_meta اضافه می‌کند و یک رکورد نمایه می‌سازد، پس اول با اجرای بدون --apply ببینید چه چیزی واجد شرایط است و بعد با --apply اعمالش کنید.

منابع

  1. انتشار Codex CLI 0.158.0 در گیت‌هاب: طبقه‌بندی خطاهای خواندن rollout و بهبودهای سندباکس — خوانده در ۱۰ مهر ۱۴۰۵
  2. انتشار Codex CLI 0.159.0 در گیت‌هاب: صفحه‌بندی تاریخ نشست از یک آیتم مشخص — خوانده در ۱۰ مهر ۱۴۰۵
  3. کمپین 48151: افزودن لنگر آیتم به صفحه‌بندی thread/items/list — خوانده در ۱۰ مهر ۱۴۰۵
  4. شماره‌ی 41814: migrate-rollouts نمی‌تواند rollout فعال بدون فراداده را بازیابی کند — باز در ۱۰ مهر ۱۴۰۵
  5. شماره‌ی 40426: کدکس نشست‌های پشتیبان‌گیری‌شده را بدون فراداده SQLite بازیابی نمی‌کند — باز در ۱۰ مهر ۱۴۰۵
  6. شماره‌ی 38761: پاک شدن نام نشست فقط از session_index پس از مهاجرت — باز در ۱۰ مهر ۱۴۰۵
  7. مستندات Codex CLI: نصب، اجرای پروژه‌ای و codex resume برای بازگشت به گفتگوی ذخیره‌شده — خوانده در ۱۰ مهر ۱۴۰۵
  8. مرجع خط فرمان کدکس: فهرست زیر‌فرمان‌ها — خوانده در ۱۰ مهر ۱۴۰۵
  9. یادداشت انتشار کدکس و چت‌جی‌پی‌تی، شهریور و مهر ۱۴۰۵ — خوانده در ۱۰ مهر ۱۴۰۵
  10. مخزن openai/codex
  11. خواندن وضعیت واقعی پرچم‌های کدکس با codex features