codex-session-cleanup is a small Codex skill for previewing retention rules and
safely archiving or deleting old local session history. The helper selects
logical session UUIDs; the installed codex command remains responsible for
rollout files, metadata, and descendant cascades.
The helper never edits Codex files, databases, indexes, or other internal state directly. Python 3.10+ and the standard library are sufficient.
Using Claude Code instead? See
claude-session-cleanup.
The projects share retention semantics through the same cleanup contract, but
their mutation capabilities differ.
| Capability | Support |
|---|---|
| Preview retention rules | Yes — read-only by default |
| Archive sessions | Yes — Codex-native and reversible |
| Permanent delete | Yes — irreversible; reviewed saved plan required |
| Filter by age | Yes |
| Keep latest N conversations | Yes — root thread plus descendants |
| Current-project filtering | Yes — exact normalized cwd |
Clone or copy the repository into the documented user skill location:
git clone https://github.com/Gares95/codex-session-cleanup \
"$HOME/.agents/skills/codex-session-cleanup"The distributable skill requires only:
SKILL.md
scripts/cleanup_sessions.py
Run the helper while remaining in the project whose sessions you want to target. Running it from the skill directory would make that directory the exact current-project scope.
Natural-language requests the skill understands include:
- “Preview Codex sessions older than 30 days.”
- “Keep my latest 5 conversations.”
- “Archive old sessions for this project.”
- “Delete sessions older than 30 days.”
For direct CLI use, set the installed location and run commands from the project whose sessions you want to target:
CLEANUP_SKILL="$HOME/.agents/skills/codex-session-cleanup"Preview current-project sessions older than 30 days without making changes:
python3 "$CLEANUP_SKILL/scripts/cleanup_sessions.py" \
--older-than 30d --project current --action archiveAfter reviewing that preview, archive the same policy with Codex's reversible native operation:
python3 "$CLEANUP_SKILL/scripts/cleanup_sessions.py" \
--older-than 30d --project current --action archive --applyPermanent deletion is irreversible and cannot use ordinary --apply. Save a
private plan, review it, and apply that exact plan:
mkdir -p .local
python3 "$CLEANUP_SKILL/scripts/cleanup_sessions.py" \
--older-than 30d --project current --action delete \
--details --save-plan .local/cleanup-plan.json
# Review the private manifest before continuing.
python3 -m json.tool .local/cleanup-plan.json
python3 "$CLEANUP_SKILL/scripts/cleanup_sessions.py" \
--apply-plan .local/cleanup-plan.jsonImportant
Compatibility status (last verified 2026-10-31): Codex CLI 0.165.0
exposes pinning through Thread.section and paginated
threadSection/list. The helper discovers the unique built-in Pinned
section and validates the installed generated schema on every invocation.
Compatibility is determined from the installed CLI because public app-server
documentation may describe a different protocol revision.
Supported selectors are deliberately narrow:
--older-than 48hor--older-than 7dselects conversations whose newest memberrecencyAtis strictly before the absolute cutoff.--keep-latest 5retains the five most recently active conversations.- Combining selectors uses intersection semantics: a conversation must be older than the cutoff and outside the latest N to be eligible.
--project currentis the default and requires an exact match with the cwd from which the helper is invoked after physical-path and Unicode NFC normalization. Parents, children, siblings, and similarly named paths do not match.--project allexplicitly considers all discoverable projects.--action archiveis the default. Permanent deletion must be explicit.
One conversation is an ordinary interactive root thread plus every spawned
descendant that Codex would affect when acting on that root. Its activity is
the newest recencyAt among all members. Each conversation consumes at most
one latest-N position, regardless of its number of descendants. Non-excluded
conversations are ranked by activity descending and then root UUID ascending.
Pinned, loaded, active, and otherwise protected conversations still consume a
rank. Excluded conversations do not.
Every in-scope conversation has one outcome:
selected: eligible for the requested action;retained: kept bywithin-latestornewer-than-cutoffpolicy rules;protected: kept because a safety rule such aspinned,running, orephemeralprevents mutation; andexcluded: not ranked because metadata, ancestry, identity, or exact scope cannot be established safely.
Precedence is excluded, protected, retained, then selected. A protected conversation still reports applicable retention reasons. Exclusion happens before ranking, so uncertain data can only move another conversation into the kept latest-N set.
Codex archive/delete operations cascade from a selected root. A conversation cannot be selected if any affected descendant:
- makes the conversation too recent or within latest N;
- is pinned, loaded, active, ephemeral, errored, or malformed;
- crosses the exact cwd boundary for
--project current; or - has missing, cyclic, conflicting, or otherwise ambiguous ancestry.
The helper never detaches or independently mutates descendants. Archive is a
Codex-native operation that Codex can reverse with codex unarchive.
Permanent delete is irreversible. This tool does not offer a separate
quarantine operation.
Root discovery requests ordinary saved interactive sources (cli and
vscode) and excludes child threads from independent selection. Once a root
is considered, descendant discovery uses every source kind advertised by the
installed schema so subagents and other spawned histories are included in the
cascade check.
Discovery uses thread/list with useStateDbOnly: true. Consequently:
- only sessions indexed by the supported Codex state database are covered;
- historical or otherwise unindexed rollout files may be omitted;
- the helper does not scan rollout files or ask Codex to repair metadata;
- archive policies consider non-archived roots and descendants only;
- delete policies consider both active and archived roots and descendants;
- current-project discovery enumerates state-DB-indexed ordinary roots so a root outside the cwd cannot hide a related descendant inside it;
- a conversation enters current scope when any member exactly matches the cwd, then any member outside that cwd excludes the whole conversation; and
- fully out-of-scope groups are omitted from the preview and ranking.
This utility does not claim to cover every file that may exist in Codex
history. Codex 0.165.0 still documents and exposes a normal thread/list mode
that scans rollout files and repairs state-database metadata. The helper does
not use that mode because a preview must not silently repair local Codex state.
There is currently no supported way to gain that extra completeness while
preserving the same strict read-only preview behavior.
Every policy invocation is a preview unless an explicit apply mode is used.
The helper requires recencyAt, exact cwd, section-based pin state, runtime
status, loaded-thread state, source/ephemeral metadata, ancestry filtering,
and pagination. Missing or malformed safety metadata stops or excludes the
affected operation rather than being guessed.
Human previews include sanitized titles, last-used time and age, project path,
archived state, root/descendant relationships, every group outcome, and shared
reason codes. --details prints full UUIDs and expanded state.
--json emits the shared stable-v1 session-cleanup/preview structure defined
under contract/. Titles are null by default and appear only with
--include-titles. Preview JSON is an audit/reporting artifact and cannot
authorize mutation. It is deliberately separate from the private saved-plan
manifest used by --save-plan and --apply-plan; JSON preview mode cannot be
combined with an apply mode.
Before mutation, cooperating cleanup processes take a per-Codex-home advisory lock. The helper re-discovers the remaining expected candidate set immediately before each root command and stops later mutations after a changed state or a Codex command failure. Partial success is reported explicitly.
The helper starts a standalone app-server for discovery. Its loaded-thread list cannot prove that an independent Codex/app-server process has not loaded a thread after discovery. Close other Codex clients before applying cleanup. The CLI's own ownership/refusal behavior remains an important final safeguard.
The installed 0.165.0 archive/delete APIs accept a thread ID but expose no conditional state token or atomic compare-and-mutate operation. A session can still change in the small interval between the last read and the Codex command. Codex ownership/refusal checks remain the final authority; the helper never retries with a less safe operation.
Direct permanent deletion with --action delete --apply is disabled. Use a
reviewed plan:
- Preview the exact roots and descendants.
- Save the preview with
--save-plan. - Review the text output and private JSON file.
- Apply that file with
--apply-plan.
The manifest records its format version, creation time, absolute cutoff,
policy, exact cwd, Codex home and CLI version, full candidate/descendant UUIDs,
and all safety-critical metadata. It is created with mode 0600 on POSIX. Its
digest detects accidental editing, but safety does not rely on the digest.
Apply also regenerates the plan from Codex and compares the full effective
candidate set.
Manifests keep the Codex-specific codex-session-cleanup-plan format; they do
not use the shared preview schema. A manifest created under older per-session
latest-N semantics is re-evaluated using conversation groups and is refused as
stale if its effective candidate set differs.
Changed membership, a new descendant, recency changes, new pin/load/activity,
project changes, or malformed metadata makes the plan stale. A stale,
applying, partially consumed, or consumed plan cannot be replayed; generate
and review a new one. Applying from a different Codex home—or, for current
project plans, a different cwd—is also refused. A Codex update requires a new
preview. Put manifests under an ignored private directory such as .local/
because they contain local paths, titles, and full session UUIDs. Plan paths
inside the resolved Codex home are refused;
the helper performs no direct writes there.
Archive remains reversible and may use --apply without a saved manifest. It
still performs locking and per-root validation, but it is not persistently
bound to a preview from an earlier command. The skill should show a separate
archive preview before applying it.
Replace <skill-directory> with the installed skill directory.
No policy flags are accepted with --apply-plan; the reviewed manifest shown
in the quick start is the policy and candidate authority.
python3 <skill-directory>/scripts/cleanup_sessions.py \
--keep-latest 5 --project currentpython3 <skill-directory>/scripts/cleanup_sessions.py \
--older-than 7d --keep-latest 5 --project currentpython3 <skill-directory>/scripts/cleanup_sessions.py \
--older-than 30d --project all --action archivepython3 <skill-directory>/scripts/cleanup_sessions.py \
--older-than 30d --project current --action delete --detailsCompact previews also list every affected descendant; --details expands IDs
and runtime metadata.
python3 <skill-directory>/scripts/cleanup_sessions.py \
--older-than 30d --project current --action delete --jsonThe document has schema: "session-cleanup/preview" and
schema_version: 1 from the stable shared contract. It reports selected,
retained, protected, and excluded groups,
their ranks and reason codes, all root/descendant members, Codex capabilities,
and Codex-specific metadata under extensions.codex.
Titles are omitted by default. Request them explicitly when needed for local review:
python3 <skill-directory>/scripts/cleanup_sessions.py \
--older-than 30d --project current --action delete \
--json --include-titlesPreview JSON contains identifiers and local project paths even without titles,
so treat it as private. It contains no conversation messages, tool output, or
file content. The private mutation manifest remains a different backend-specific
document created only with --save-plan.
python3 <skill-directory>/scripts/cleanup_sessions.py --check-compatibilityThe check validates the generated schema, mutation command shapes, Codex home, built-in pinned section, and loaded-thread API without mutating sessions.
Operational errors return nonzero. In particular, invalid/replayed manifests
return 2, stale-plan refusals return 3, and advisory-lock conflicts return
4. A mutation failure or safety stop returns 1.
- Python 3.10 or newer.
- An installed
codexexecutable onPATH, or a validatedcodex/codex.jsentry point under$CODEX_MANAGED_PACKAGE_ROOT/bin. - A compatible generated app-server schema.
- UUID-based
codex archiveandcodex delete --forcecommands.
The helper trusts the app-server's initialized codexHome and verifies an
explicit CODEX_HOME override. It never assumes a hard-coded Codex home.
Run the standard-library suite, including the isolated fake Codex integration:
python3 -m unittest discover -s tests -vThe suite verifies the vendored contract checksums, all 15 shared conformance fixtures, shared preview schema shape, saved-plan replay/staleness rules, and isolated fake-Codex mutation behavior.
Validate skill frontmatter with the quick_validate.py helper bundled with
Codex's skill-creator skill. Tests use fictional UUIDs and temporary state;
they never destructively test personal Codex sessions.
MIT. Copyright (c) 2026 Gares95.