Renaming Codex Threads in VS Code (Without a Built‑In Feature)
Renaming Codex Threads in VS Code (Without a Built‑In Feature)
I ran into a small but annoying issue in VS Code: Codex thread names are auto‑generated, and there’s no way to rename them. When you’re switching between multiple projects, that makes history hard to scan and easy to misclick.
Rather than waiting on a feature request, I built a small local workaround.
Codex keeps a local session index at ~/.codex/session_index.jsonl. Each entry includes a short thread_name and a thread ID. Instead of rewriting old entries, I append a newer label for the same ID. The UI uses the newest label, so the history list becomes readable without losing any of the original trail. I also make a timestamped backup before each change, so it’s reversible.
I wrapped that into a tiny helper so I never have to hand‑edit the file again:
rename-codex-thread --id <thread-id> --name "Aethra"
cat <<'EOF' > ~/bin/rename-codex-thread && chmod +x ~/bin/rename-codex-thread
#!/usr/bin/env bash
set -euo pipefail
usage(){ cat <<'USAGE'
Usage: rename-codex-thread --id <thread-id> --name <thread-name> [--timestamp <iso-8601>] [--index <path>] [--state-db <path>] [--no-backup] [--dry-run]
USAGE
}
json_escape(){ local v="$1"; v="${v//\\/\\\\}"; v="${v//\"/\\\"}"; v="${v//$'\n'/\\n}"; v="${v//$'\r'/\\r}"; v="${v//$'\t'/\\t}"; printf '%s' "$v"; }
codex_home="${CODEX_HOME:-$HOME/.codex}"
index_file="$codex_home/session_index.jsonl"
state_db="$codex_home/state_5.sqlite"
thread_id=''; thread_name=''; timestamp=''; backup_enabled=1; dry_run=0
while [ $# -gt 0 ]; do
case "$1" in
--id) thread_id="${2:-}"; shift 2;;
--name) thread_name="${2:-}"; shift 2;;
--timestamp) timestamp="${2:-}"; shift 2;;
--index) index_file="${2:-}"; shift 2;;
--state-db) state_db="${2:-}"; shift 2;;
--no-backup) backup_enabled=0; shift;;
--dry-run) dry_run=1; shift;;
-h|--help) usage; exit 0;;
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1;;
esac
done
if [ -z "$thread_id" ] || [ -z "$thread_name" ]; then usage >&2; exit 1; fi
if [ ! -f "$index_file" ]; then echo "Missing session index: $index_file" >&2; exit 1; fi
thread_exists=0
if [ -f "$state_db" ] && command -v sqlite3 >/dev/null 2>&1; then
if sqlite3 "$state_db" "select 1 from threads where id = '$thread_id' limit 1;" | grep -q '^1$'; then thread_exists=1; fi
fi
if [ "$thread_exists" -ne 1 ] && ! grep -q "\"id\":\"$thread_id\"" "$index_file"; then
echo "Thread id not found in state DB or session index: $thread_id" >&2; exit 1
fi
previous_name="$(awk -v id="$thread_id" '
$0 ~ "\"id\":\"" id "\"" { if (match($0, /"thread_name":"([^"]*)"/)) { value = substr($0, RSTART + 15, RLENGTH - 16) } }
END { if (value != "") print value }
' "$index_file")"
if [ -z "$timestamp" ]; then timestamp="$(date -u +"%Y-%m-%dT%H:%M:%S.000000Z")"; fi
escaped_name="$(json_escape "$thread_name")"
entry="$(printf '{"id":"%s","thread_name":"%s","updated_at":"%s"}' "$thread_id" "$escaped_name" "$timestamp")"
if [ "$dry_run" -eq 1 ]; then
printf 'Would append: %s\n' "$entry"
[ -n "$previous_name" ] && printf 'Previous label: %s\n' "$previous_name"
exit 0
fi
if [ "$backup_enabled" -eq 1 ]; then
backup_file="$index_file.bak-rename-$(date +%Y%m%d-%H%M%S)"
cp "$index_file" "$backup_file"
printf 'Backup created: %s\n' "$backup_file"
fi
printf '%s\n' "$entry" >> "$index_file"
printf 'Renamed thread %s\nOld label: %s\nNew label: %s\n' "$thread_id" "${previous_name:-<none>}" "$thread_name"
EOF