1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
| import json
import shutil
import sqlite3
import subprocess
from datetime import datetime
from pathlib import Path
result = subprocess.run(
["tasklist", "/FI", "IMAGENAME eq codex.exe", "/FO", "CSV", "/NH"],
capture_output=True,
text=True,
errors="replace",
check=False,
)
if any(line.lstrip().startswith('"') for line in result.stdout.splitlines()):
raise SystemExit("ERROR: codex.exe is still running. Exit Codex completely, then run again.")
codex_home = Path.home() / ".codex"
db_path = codex_home / "state_5.sqlite"
if not db_path.is_file():
raise SystemExit(f"ERROR: database not found: {db_path}")
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
backup_dir = codex_home / "repair-backups" / f"manual-normalize-rollout-paths-{stamp}"
backup_dir.mkdir(parents=True, exist_ok=False)
backup_db = backup_dir / "state_5.sqlite.before-repair"
with sqlite3.connect(db_path) as source, sqlite3.connect(backup_db) as destination:
source.backup(destination)
for suffix in ("-wal", "-shm"):
source_path = Path(str(db_path) + suffix)
if source_path.exists():
shutil.copy2(source_path, backup_dir / source_path.name)
prefix = "\\\\?\\"
with sqlite3.connect(db_path) as conn:
conn.execute("PRAGMA busy_timeout = 10000")
rows = conn.execute(
"SELECT id, rollout_path FROM threads WHERE archived = 0"
).fetchall()
fixes = []
skipped = []
for thread_id, old_path in rows:
if not old_path or not old_path.startswith(prefix):
continue
new_path = old_path[len(prefix):]
if Path(new_path).is_file():
fixes.append((thread_id, old_path, new_path))
else:
skipped.append((thread_id, old_path))
conn.execute("BEGIN IMMEDIATE")
for thread_id, old_path, new_path in fixes:
conn.execute(
"""
UPDATE threads
SET rollout_path = ?
WHERE id = ?
AND archived = 0
AND rollout_path = ?
""",
(new_path, thread_id, old_path),
)
conn.commit()
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
remaining_rows = conn.execute(
"SELECT rollout_path FROM threads WHERE archived = 0"
).fetchall()
remaining = sum(
1
for (path,) in remaining_rows
if path and path.startswith(prefix)
)
report = {
"updated_count": len(fixes),
"skipped_missing_files": len(skipped),
"remaining_prefixed_active_paths": remaining,
"backup_dir": str(backup_dir),
}
(backup_dir / "result.json").write_text(
json.dumps(report, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print("Updated paths:", len(fixes))
print("Skipped missing files:", len(skipped))
print("Remaining active prefixed paths:", remaining)
print("Backup and report:", backup_dir)
|