@@ -1002,9 +1002,102 @@ assert_eq "Codex resume extraction" "ses_codex_789" "$(get_codex_session 99999 "
10021002assert_eq " Codex resume with path" " ses_codex_789" " $( get_codex_session 99999 " /usr/bin/codex resume ses_codex_789" ) "
10031003assert_eq " Codex bare (no resume)" " " " $( get_codex_session 99999 " codex" ) "
10041004
1005- # --- Codex: rollout session files (Method 3) ---
1006- # Newer Codex versions write session metadata to ~/.codex/sessions/*/*.jsonl
1007- # instead of session-tags.jsonl. Test that get_codex_session can find them.
1005+ # --- Codex: state_*.sqlite thread DB (Method 3) ---
1006+ # Codex >= ~0.118 persists thread state in SQLite. The save script queries
1007+ # the threads table by cwd, preferring recently-updated unarchived threads.
1008+
1009+ echo " "
1010+ echo " === Codex state DB: thread lookup via state_*.sqlite ==="
1011+ echo " "
1012+
1013+ STATEDB_TEST_DIR=$( mktemp -d)
1014+ mkdir -p " $STATEDB_TEST_DIR /.codex"
1015+
1016+ # Create a test state DB with the threads table
1017+ python3 - " $STATEDB_TEST_DIR /.codex/state_5.sqlite" << 'DBSETUP '
1018+ import sqlite3, sys, time
1019+ db = sys.argv[1]
1020+ conn = sqlite3.connect(db)
1021+ conn.execute('''CREATE TABLE threads (
1022+ id TEXT PRIMARY KEY,
1023+ rollout_path TEXT NOT NULL,
1024+ created_at INTEGER NOT NULL,
1025+ updated_at INTEGER NOT NULL,
1026+ source TEXT NOT NULL,
1027+ model_provider TEXT NOT NULL,
1028+ cwd TEXT NOT NULL,
1029+ title TEXT NOT NULL,
1030+ sandbox_policy TEXT NOT NULL,
1031+ approval_mode TEXT NOT NULL,
1032+ tokens_used INTEGER NOT NULL DEFAULT 0,
1033+ has_user_event INTEGER NOT NULL DEFAULT 0,
1034+ archived INTEGER NOT NULL DEFAULT 0,
1035+ archived_at INTEGER
1036+ )''')
1037+ now = int(time.time())
1038+ # Active thread matching test cwd — updated recently
1039+ conn.execute('''INSERT INTO threads (id, rollout_path, created_at, updated_at, source,
1040+ model_provider, cwd, title, sandbox_policy, approval_mode)
1041+ VALUES (?, '', ?, ?, 'cli', 'openai', '/tmp/statedb-project', 'active', 'relaxed', 'auto')''',
1042+ ('ses_statedb_active', now - 3600, now - 10))
1043+ # Older thread same cwd — should lose to the active one
1044+ conn.execute('''INSERT INTO threads (id, rollout_path, created_at, updated_at, source,
1045+ model_provider, cwd, title, sandbox_policy, approval_mode)
1046+ VALUES (?, '', ?, ?, 'cli', 'openai', '/tmp/statedb-project', 'old', 'relaxed', 'auto')''',
1047+ ('ses_statedb_old', now - 86400, now - 86400))
1048+ # Archived thread same cwd — should be excluded
1049+ conn.execute('''INSERT INTO threads (id, rollout_path, created_at, updated_at, source,
1050+ model_provider, cwd, title, sandbox_policy, approval_mode, archived, archived_at)
1051+ VALUES (?, '', ?, ?, 'cli', 'openai', '/tmp/statedb-project', 'archived', 'relaxed', 'auto', 1, ?)''',
1052+ ('ses_statedb_archived', now - 7200, now - 5, now - 5))
1053+ # Thread in different cwd — should not match
1054+ conn.execute('''INSERT INTO threads (id, rollout_path, created_at, updated_at, source,
1055+ model_provider, cwd, title, sandbox_policy, approval_mode)
1056+ VALUES (?, '', ?, ?, 'cli', 'openai', '/tmp/other-project', 'other', 'relaxed', 'auto')''',
1057+ ('ses_statedb_other', now - 100, now - 1))
1058+ conn.commit()
1059+ conn.close()
1060+ DBSETUP
1061+
1062+ ORIG_HOME=" $HOME "
1063+ HOME=" $STATEDB_TEST_DIR "
1064+
1065+ # Should find the most recently updated active thread for the matching cwd
1066+ statedb_sid=$( get_codex_session $$ " codex" " /tmp/statedb-project" )
1067+ assert_eq " Codex state DB: finds active thread by cwd" " ses_statedb_active" " $statedb_sid "
1068+
1069+ # Should NOT match a different cwd
1070+ statedb_miss=$( get_codex_session $$ " codex" " /tmp/nonexistent" )
1071+ assert_eq " Codex state DB: no match for different cwd" " " " $statedb_miss "
1072+
1073+ # Dedup: after claiming ses_statedb_active, next call should get ses_statedb_old
1074+ USED_CODEX_SESSION_IDS=" "
1075+ statedb_first=$( get_codex_session $$ " codex" " /tmp/statedb-project" )
1076+ register_codex_session_id " $statedb_first "
1077+ statedb_second=$( get_codex_session $$ " codex" " /tmp/statedb-project" )
1078+
1079+ if [ -n " $statedb_first " ] && [ -n " $statedb_second " ] && [ " $statedb_first " != " $statedb_second " ]; then
1080+ pass " Codex state DB dedup: two calls get distinct sessions ($statedb_first vs $statedb_second )"
1081+ else
1082+ fail " Codex state DB dedup: expected distinct sessions, got '$statedb_first ' and '$statedb_second '"
1083+ fi
1084+ USED_CODEX_SESSION_IDS=" "
1085+
1086+ # Should prefer state DB (Method 3) over rollout JSONL (Method 4) when both exist
1087+ mkdir -p " $STATEDB_TEST_DIR /.codex/sessions/2026/04/23"
1088+ cat > " $STATEDB_TEST_DIR /.codex/sessions/2026/04/23/rollout-statedb-test.jsonl" << 'ROLLOUT '
1089+ {"timestamp":"2026-04-23T10:00:00.000Z","type":"session_meta","payload":{"id":"ses_rollout_loser","timestamp":"2026-04-23T10:00:00.000Z","cwd":"/tmp/statedb-project","originator":"codex_cli_rs","cli_version":"0.116.0"}}
1090+ ROLLOUT
1091+
1092+ statedb_priority=$( get_codex_session $$ " codex" " /tmp/statedb-project" )
1093+ assert_eq " Codex state DB takes priority over rollout JSONL" " ses_statedb_active" " $statedb_priority "
1094+
1095+ HOME=" $ORIG_HOME "
1096+ rm -rf " $STATEDB_TEST_DIR "
1097+
1098+ # --- Codex: rollout session files (Method 4) ---
1099+ # Codex ~0.100-0.117 wrote session metadata to ~/.codex/sessions/*/*.jsonl.
1100+ # Newer versions use SQLite (Method 3). Test the JSONL fallback.
10081101
10091102ROLLOUT_TEST_DIR=$( mktemp -d)
10101103mkdir -p " $ROLLOUT_TEST_DIR /.codex/sessions/2026/03/24"
0 commit comments