python脚本代码
from pathlib import Path
from datetime import datetime
import json
import shutil
import os
ROOT = Path.home() / ".codex" / "sessions"
if not ROOT.exists():
raise SystemExit(f"Codex sessions directory not found: {ROOT}")
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
BACKUP_ROOT = ROOT.parent / f"sessions-backup-{stamp}"
print("=" * 100)
print("Codex Session Repair")
print("=" * 100)
print(f"Sessions : {ROOT}")
print(f"Backup : {BACKUP_ROOT}")
print()
# ----------------------------------------------------------------------
# 1. 完整备份整个 sessions 目录
# ----------------------------------------------------------------------
print("Creating full backup...")
shutil.copytree(ROOT, BACKUP_ROOT)
print(f"Backup created: {BACKUP_ROOT}")
print()
# ----------------------------------------------------------------------
# 2. 找到所有 rollout
# ----------------------------------------------------------------------
files = sorted(ROOT.rglob("rollout-*.jsonl"))
if not files:
raise SystemExit("No rollout files found.")
print(f"Found {len(files)} rollout files.")
print()
total_files = 0
changed_files = 0
total_reasoning = 0
removed_reasoning_encrypted = 0
total_compaction = 0
removed_compaction_encrypted = 0
# ----------------------------------------------------------------------
# 3. 判断 / 修复 encrypted_content
#
# 只处理:
# type=response_item, payload.type=reasoning
# type=response_item, payload.type=compaction
#
# 同时递归处理 compaction 中嵌套的 replacement_history。
# ----------------------------------------------------------------------
def sanitize(node):
"""
Remove provider-specific encrypted_content only from
reasoning / compaction structures.
Returns:
(new_node, changed, reasoning_count, reasoning_removed,
compaction_count, compaction_removed)
"""
changed = False
reasoning_count = 0
reasoning_removed = 0
compaction_count = 0
compaction_removed = 0
if isinstance(node, dict):
# 判断当前节点是不是 reasoning / compaction
node_type = node.get("type")
is_reasoning = node_type == "reasoning"
is_compaction = node_type == "compaction"
if is_reasoning:
reasoning_count += 1
if "encrypted_content" in node:
del node["encrypted_content"]
changed = True
reasoning_removed += 1
if is_compaction:
compaction_count += 1
if "encrypted_content" in node:
del node["encrypted_content"]
changed = True
compaction_removed += 1
# 继续递归处理所有子节点
for key in list(node.keys()):
new_value, c, rc, rr, cc, cr = sanitize(node[key])
node[key] = new_value
changed = changed or c
reasoning_count += rc
reasoning_removed += rr
compaction_count += cc
compaction_removed += cr
return (
node,
changed,
reasoning_count,
reasoning_removed,
compaction_count,
compaction_removed,
)
elif isinstance(node, list):
new_list = []
for item in node:
new_item, c, rc, rr, cc, cr = sanitize(item)
new_list.append(new_item)
changed = changed or c
reasoning_count += rc
reasoning_removed += rr
compaction_count += cc
compaction_removed += cr
return (
new_list,
changed,
reasoning_count,
reasoning_removed,
compaction_count,
compaction_removed,
)
return (
node,
False,
0,
0,
0,
0,
)
# ----------------------------------------------------------------------
# 4. 逐个修复
# ----------------------------------------------------------------------
for src in files:
total_files += 1
tmp = src.with_suffix(".repairing.jsonl")
file_changed = False
file_reasoning = 0
file_reasoning_removed = 0
file_compaction = 0
file_compaction_removed = 0
line_count = 0
try:
with (
src.open("r", encoding="utf-8", errors="strict") as fin,
tmp.open("w", encoding="utf-8", newline="\n") as fout,
):
for line_no, line in enumerate(fin, 1):
line_count += 1
try:
obj = json.loads(line)
except Exception as e:
raise RuntimeError(
f"Invalid JSON: {src} line {line_no}: {e}"
)
(
obj,
changed,
rc,
rr,
cc,
cr,
) = sanitize(obj)
file_changed = file_changed or changed
file_reasoning += rc
file_reasoning_removed += rr
file_compaction += cc
file_compaction_removed += cr
fout.write(
json.dumps(
obj,
ensure_ascii=False,
separators=(",", ":"),
)
+ "\n"
)
# ------------------------------------------------------------------
# 5. 验证临时文件
# ------------------------------------------------------------------
with tmp.open("r", encoding="utf-8") as f:
for line_no, line in enumerate(f, 1):
try:
json.loads(line)
except Exception as e:
raise RuntimeError(
f"Validation failed: {tmp} line {line_no}: {e}"
)
# ------------------------------------------------------------------
# 6. 验证:目标结构里不能再存在 encrypted_content
# ------------------------------------------------------------------
remaining_encrypted = 0
with tmp.open("r", encoding="utf-8") as f:
for line in f:
obj = json.loads(line)
def count_target_encrypted(node):
count = 0
if isinstance(node, dict):
t = node.get("type")
if t in ("reasoning", "compaction"):
if "encrypted_content" in node:
count += 1
for value in node.values():
count += count_target_encrypted(value)
elif isinstance(node, list):
for value in node:
count += count_target_encrypted(value)
return count
remaining_encrypted += count_target_encrypted(obj)
if remaining_encrypted != 0:
raise RuntimeError(
f"Repair verification failed for {src}: "
f"{remaining_encrypted} encrypted fields remain."
)
# ------------------------------------------------------------------
# 7. 没修改就删除临时文件
# ------------------------------------------------------------------
if not file_changed:
tmp.unlink(missing_ok=True)
print(f"[SKIP] {src}")
print(" no provider-specific encrypted reasoning found")
print()
continue
# ------------------------------------------------------------------
# 8. 原子替换
# ------------------------------------------------------------------
os.replace(tmp, src)
changed_files += 1
total_reasoning += file_reasoning
removed_reasoning_encrypted += file_reasoning_removed
total_compaction += file_compaction
removed_compaction_encrypted += file_compaction_removed
print(f"[FIXED] {src}")
print(f" lines : {line_count:,}")
print(f" reasoning : {file_reasoning}")
print(f" reasoning encrypted : {file_reasoning_removed}")
print(f" compaction : {file_compaction}")
print(f" compaction encrypted : {file_compaction_removed}")
print()
except (PermissionError, OSError) as e:
tmp.unlink(missing_ok=True)
print(f"[SKIP - INACCESSIBLE] {src}")
print(f" Reason: {e}\n")
continue
except Exception:
tmp.unlink(missing_ok=True)
raise
# ----------------------------------------------------------------------
# 9. 最终结果
# ----------------------------------------------------------------------
print("=" * 100)
print("DONE")
print("=" * 100)
print(f"Total rollout files : {total_files}")
print(f"Changed files : {changed_files}")
print(f"Reasoning items : {total_reasoning}")
print(f"Reasoning encrypted rm : {removed_reasoning_encrypted}")
print(f"Compaction items : {total_compaction}")
print(f"Compaction encrypted rm : {removed_compaction_encrypted}")
print()
print(f"FULL BACKUP : {BACKUP_ROOT}")
print()
print("Original sessions were repaired in-place.")
print("Full backup is available for rollback.")