Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ The script reports title and preview payload size in report mode and normal appl
python scripts/keep_codex_fast.py --apply --repair-thread-metadata-bloat
```

With that flag, after backing up and only when Codex is not running, it trims active SQLite title/preview metadata to bounded display values. It also appends repaired titles to `session_index.jsonl`, which matches current Codex name-update storage.
With that flag, after backing up and only when Codex is not running, it trims active SQLite title/preview metadata to bounded display values. If a thread already has a friendly name in `session_index.jsonl`, the repair writes that name back into the SQLite display title instead of replacing it with a shortened prompt, including already-bounded prompt fallback titles from earlier repairs.

This does not remove the actual conversation transcript. The full rollout JSONL remains available unless you separately archive the session.

Expand Down
2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Normal apply mode reports metadata-bloat candidates but does not repair them. If
python scripts/keep_codex_fast.py --apply --repair-thread-metadata-bloat
```

That bounds active `threads.title` and `threads.first_user_message` values. Defaults are 120 characters for titles and 240 characters for previews. It also appends repaired titles to `session_index.jsonl`, matching current upstream name-update storage, so reconciliation/name lookup does not immediately prefer the old full-message fallback.
That bounds active `threads.title` and `threads.first_user_message` values. Defaults are 120 characters for titles and 240 characters for previews. If a thread already has a friendly name in `session_index.jsonl`, the repair writes that name back into the SQLite display title instead of replacing it with a shortened prompt, including already-bounded prompt fallback titles from earlier repairs.

The targeted repair manifest stores the old full title/preview values so the change can be reversed. Treat `thread-metadata-repairs.jsonl`, `restore-thread-metadata.py`, and the whole backup folder as private local artifacts.

Expand Down
50 changes: 41 additions & 9 deletions scripts/keep_codex_fast.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,42 @@ def append_session_index_name(codex_home: Path, thread_id: str, name: str) -> No
handle.write(json.dumps(entry, ensure_ascii=False) + "\n")


def latest_session_index_name(codex_home: Path, thread_id: str) -> str | None:
path = codex_home / "session_index.jsonl"
if not path.exists():
return None
latest = None
try:
lines = path.read_text(encoding="utf-8").splitlines()
except OSError:
return None
for line in lines:
try:
record = json.loads(line)
except json.JSONDecodeError:
continue
if record.get("id") == thread_id and record.get("thread_name"):
latest = str(record["thread_name"])
return latest


def should_append_repaired_session_index_name(
codex_home: Path,
item: ThreadMetadataRepair,
) -> bool:
if not item.new_title or item.new_title == item.old_title:
return False
existing_name = latest_session_index_name(codex_home, item.thread_id)
return existing_name is None or existing_name == item.old_title


def repaired_thread_title(codex_home: Path, thread_id: str, old_title: str, title_limit: int) -> str:
existing_name = latest_session_index_name(codex_home, thread_id)
if existing_name:
return bounded_text(existing_name, title_limit)
return bounded_text(old_title, title_limit)


def report_thread_metadata_bloat(
conn: sqlite3.Connection,
*,
Expand Down Expand Up @@ -343,24 +379,20 @@ def repair_thread_metadata_bloat(
select id, title, {select_preview}
from threads
where {archived_expr}
and (
length(title) > ?
{"or length(first_user_message) > ?" if has_preview else ""}
)
""",
(title_limit, preview_limit) if has_preview else (title_limit,),
"""
).fetchall()

repairs: list[ThreadMetadataRepair] = []
for thread_id, title, preview in rows:
thread_id = str(thread_id)
old_title = title or ""
old_preview = preview or ""
new_title = bounded_text(old_title, title_limit)
new_title = repaired_thread_title(codex_home, thread_id, old_title, title_limit)
new_preview = bounded_text(old_preview, preview_limit) if has_preview else ""
if new_title != old_title or new_preview != old_preview:
repairs.append(
ThreadMetadataRepair(
str(thread_id),
thread_id,
old_title,
new_title,
old_preview,
Expand Down Expand Up @@ -411,7 +443,7 @@ def repair_thread_metadata_bloat(
"update threads set title=? where id=?",
(item.new_title, item.thread_id),
)
if item.new_title and item.new_title != item.old_title:
if should_append_repaired_session_index_name(codex_home, item):
append_session_index_name(codex_home, item.thread_id, item.new_title)
report("thread_metadata_repair applied")
report(f"thread_metadata_repair_manifest {manifest}")
Expand Down
91 changes: 87 additions & 4 deletions tests/smoke_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import contextlib
import io
import importlib.util
import json
import os
import sqlite3
import sys
Expand Down Expand Up @@ -85,6 +86,21 @@ def make_fake_home(root: Path) -> dict[str, Path]:
}


def latest_session_index_name(codex_home: Path, thread_id: str) -> str | None:
path = codex_home / "session_index.jsonl"
if not path.exists():
return None
name = None
for line in path.read_text(encoding="utf-8").splitlines():
try:
record = json.loads(line)
except json.JSONDecodeError:
continue
if record.get("id") == thread_id and record.get("thread_name"):
name = record["thread_name"]
return name


def assert_report_mode(module) -> None:
with tempfile.TemporaryDirectory() as td:
paths = make_fake_home(Path(td))
Expand Down Expand Up @@ -183,6 +199,10 @@ def assert_apply_mode(module) -> None:
with tempfile.TemporaryDirectory() as td:
paths = make_fake_home(Path(td))
backup = Path(td) / "backup-apply"
(paths["codex_home"] / "session_index.jsonl").write_text(
'{"id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","thread_name":"Friendly Agent","updated_at":"2026-01-01T00:00:00.000Z"}\n',
encoding="utf-8",
)
args = argparse.Namespace(
apply=True,
backup_only=False,
Expand Down Expand Up @@ -210,7 +230,7 @@ def assert_apply_mode(module) -> None:
assert archived_at is not None
assert "archived_sessions" in rollout_path
assert cwd == r"C:\DefinitelyMissingKeepCodexFast"
assert len(title) <= 120
assert title == "Friendly Agent"
assert len(preview) <= 240
assert not paths["rollout"].exists()
assert not paths["worktree"].exists()
Expand All @@ -223,9 +243,70 @@ def assert_apply_mode(module) -> None:
assert (backup / "moved-sessions.jsonl").exists()
assert (backup / "thread-metadata-repairs.jsonl").exists()
assert (backup / "moved-worktrees.jsonl").exists()
session_index = paths["codex_home"] / "session_index.jsonl"
assert session_index.exists()
assert "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" in session_index.read_text(encoding="utf-8")
assert latest_session_index_name(paths["codex_home"], "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") == "Friendly Agent"


def assert_repair_adds_bounded_name_when_no_existing_name(module) -> None:
with tempfile.TemporaryDirectory() as td:
paths = make_fake_home(Path(td))
backup = Path(td) / "backup-repair-name"
args = argparse.Namespace(
apply=True,
backup_only=False,
details=False,
wait_for_codex_exit=False,
codex_home=str(paths["codex_home"]),
backup_root=str(backup),
archive_older_than_days=10,
worktree_older_than_days=7,
rotate_logs_above_mb=64,
thread_title_limit=120,
thread_preview_limit=240,
repair_thread_metadata_bloat=True,
)
assert module.run(args) == 0
name = latest_session_index_name(paths["codex_home"], "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
assert name is not None
assert len(name) <= 120


def assert_repair_restores_existing_name_when_title_is_already_bounded(module) -> None:
with tempfile.TemporaryDirectory() as td:
paths = make_fake_home(Path(td))
backup = Path(td) / "backup-repair-existing-name"
conn = sqlite3.connect(paths["state_db"])
conn.execute(
"update threads set title=?, first_user_message=? where id=?",
("Short prompt fallback", "Short preview", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
)
conn.commit()
conn.close()
(paths["codex_home"] / "session_index.jsonl").write_text(
'{"id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","thread_name":"Friendly Agent","updated_at":"2026-01-01T00:00:00.000Z"}\n',
encoding="utf-8",
)
args = argparse.Namespace(
apply=True,
backup_only=False,
details=False,
wait_for_codex_exit=False,
codex_home=str(paths["codex_home"]),
backup_root=str(backup),
archive_older_than_days=10,
worktree_older_than_days=7,
rotate_logs_above_mb=64,
thread_title_limit=120,
thread_preview_limit=240,
repair_thread_metadata_bloat=True,
)
assert module.run(args) == 0
conn = sqlite3.connect(paths["state_db"])
title = conn.execute(
"select title from threads where id=?",
("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",),
).fetchone()[0]
conn.close()
assert title == "Friendly Agent"


def assert_normal_apply_does_not_repair_thread_metadata(module) -> None:
Expand Down Expand Up @@ -267,6 +348,8 @@ def main() -> int:
assert_backup_only_mode(module)
assert_session_alias_detection(module)
assert_normal_apply_does_not_repair_thread_metadata(module)
assert_repair_adds_bounded_name_when_no_existing_name(module)
assert_repair_restores_existing_name_when_title_is_already_bounded(module)
assert_apply_mode(module)
print("smoke tests passed")
return 0
Expand Down