DEV Community

Little Pan
Little Pan

Posted on Edited on Originally published at ponymux.com

How to bring back your Claude Code, Codex, and Grok sessions after a restart

On Friday you had four agents going in three repos: two Claude Code sessions, a Codex session running tests, and Grok halfway through a refactor. Over the weekend the computer installed an update and restarted. On Monday every conversation is still on disk, but nothing tells you which four were running, or in which folders.

  • What's saved: every message up to the restart. What's gone: the running processes, and any record of which sessions were open.
  • One session: in its folder, run claude --continue, codex resume --last, or grok --continue.
  • The whole group: list your recent sessions with their folders. Or, before the next restart, let each agent's hooks keep a list of the sessions that are still open.
  • Reopen them together: one tab per session, each in its own folder. On Windows, one wt command opens them all.
  • In PonyMux, on Mac and Windows, every Terminal keeps its folder and session. After a restart, the agents that were running wait under Suspended, and Wake All resumes them one by one.

What does a restart take away?

Closing a terminal window, quitting the terminal app, logging out, and restarting all end the processes inside, Claude Code, Codex, and Grok included. Each agent writes its transcript as it goes, so the conversation up to that moment stays on disk. A reply that was still being written is cut off.

What no tool keeps is a list of the sessions that were open. Claude Code, Codex, and Grok store sessions by folder or by date, but none of them marks which ones were running when the computer went down. That list is the part you have to rebuild.

Sleep is different. When the Mac or PC sleeps, processes pause and carry on when it wakes. If an agent reports a network error after wake, tell it to continue.

Some terminal apps reopen your windows after a restart, with the same folders and scrollback. That looks like the session is back, but each window holds a fresh shell. The agent isn't running in it.

How do I bring one session back?

Open a terminal in the project folder and run:

# Claude Code
claude --continue

# Codex
codex resume --last

# Grok
grok --continue
Enter fullscreen mode Exit fullscreen mode

Each one picks the most recent session in the current folder, and works the same in PowerShell on Windows. The folder matters: run the same command from your home folder and you get a different session, or none. If you ran more than one session in that folder, use the picker instead: claude --resume, codex resume, or /resume inside Grok.

How do I find every session I had open?

After a restart, the transcripts are all you have. On macOS or Linux, these commands print one ready-to-run line per recent session, most recently active first:

# Claude Code: the 10 most recent sessions
ls -t ~/.claude/projects/*/*.jsonl | head -10 | while read -r f; do
  jq -r 'select(.cwd) | "cd \(.cwd | @sh) && claude --resume \(.sessionId)"' "$f" | head -1
done
Enter fullscreen mode Exit fullscreen mode
# Codex: the 10 most recent sessions
ls -t ~/.codex/sessions/*/*/*/*.jsonl | head -10 | while read -r f; do
  jq -r 'select(.type == "session_meta") | "cd \(.payload.cwd | @sh) && codex resume \(.payload.id)"' "$f" | head -1
done
Enter fullscreen mode Exit fullscreen mode
# Grok: the 10 most recent sessions, without subagents' sessions
subagents=$(find ~/.grok/sessions -mindepth 4 -maxdepth 4 -path '*/subagents/*' -exec basename {} \;)
ls -t ~/.grok/sessions/*/*/summary.json | while read -r f; do
  id=$(jq -r '.info.id' "$f")
  printf '%s\n' "$subagents" | grep -qx "$id" && continue
  jq -r '"cd \(.info.cwd | @sh) && grok --resume \(.info.id)"' "$f"
done | head -10
Enter fullscreen mode Exit fullscreen mode

Each line looks like cd '/Users/you/code/api' && claude --resume 8e539d93-…. Open a tab for each session you want back and paste its line. Skip lines whose folder no longer exists, such as a worktree you've since removed.

The transcripts tell you which sessions you used recently, not which ones were still open, so you have to pick. The commands also read each tool's internal session files, which can change between releases. Grok keeps a subagent's work as a session of its own, which is why its command skips those. All three need jq, which ships with macOS 15 and later.

On Windows, use each agent's picker instead: press Ctrl+A in claude --resume to see every project, run codex resume --all, or open /resume in Grok.

How do I keep a list for the next restart?

Claude Code, Codex, and Grok all run hooks when a session starts and when it ends. A short script can use them to keep a list: one small file per session, holding its agent, folder, and ID. It adds the file when the session starts and removes it when you quit the session yourself. A restart, a closed window, or a crash leaves the file in place, so after the next restart the folder holds the sessions that were open. One file per session also means agents that start at the same moment can't overwrite each other's entries.

On macOS or Linux, run mkdir -p ~/.agent-sessions, save this as ~/.agent-sessions/track, and run chmod +x ~/.agent-sessions/track:

#!/bin/sh
# Run by each agent's SessionStart and SessionEnd hooks: track <agent> start|end
# Keeps one file per open session in ~/.agent-sessions/open: its agent, folder, and ID.
list="$HOME/.agent-sessions/open"
agent=$1
[ -n "$GROK_HOOK_EVENT" ] && agent=grok   # Grok also runs Claude Code's hooks
json=$(cat)
id=$(printf '%s' "$json" | jq -r '.session_id // .sessionId // empty')
dir=$(printf '%s' "$json" | jq -r '.cwd // empty')
reason=$(printf '%s' "$json" | jq -r '.reason // empty')
case $id in '' | */*) exit 0 ;; esac
case "$agent $2 $reason" in
  *" start "*)   # a session started: add it
    mkdir -p "$list" && printf '%s\t%s\t%s\n' "$agent" "$dir" "$id" > "$list/$id" ;;
  "claude end prompt_input_exit" | "claude end clear" | "claude end resume" | \
  "claude end logout" | "codex end "*)   # you quit or switched away from it: remove it
    rm -f "$list/$id" ;;
esac   # any other end is a restart or a closed window: keep it
Enter fullscreen mode Exit fullscreen mode

Then add the two hooks to each agent. For Claude Code, in ~/.claude/settings.json:

{
  "hooks": {
    "SessionStart": [{ "hooks": [{ "type": "command", "command": "~/.agent-sessions/track claude start" }] }],
    "SessionEnd": [{ "hooks": [{ "type": "command", "command": "~/.agent-sessions/track claude end" }] }]
  }
}
Enter fullscreen mode Exit fullscreen mode

Use the same block, with codex in place of claude, in ~/.codex/hooks.json, and with grok in a new file, ~/.grok/hooks/agent-sessions.json. If a file already has a "hooks" section, add the two entries to it rather than replacing it.

A few things behave differently per agent:

  • Codex skips new hooks until you trust them. Open /hooks in Codex and trust both. Codex also starts a session, and runs its start hook, only after your first message.
  • Claude Code reports why a session ended, so the script can tell /exit, /clear, or switching with /resume from a closed window. Codex runs its end hook only when you quit. Grok reports every exit the same way, so a Grok session you quit stays on the list.
  • Grok also runs the hooks in Claude Code's settings. The script notices and records those sessions as Grok.

On Windows, save the PowerShell version as %USERPROFILE%\.agent-sessions\track.ps1:

# Run by each agent's SessionStart and SessionEnd hooks: track.ps1 <agent> start|end
# Keeps one file per open session in ~\.agent-sessions\open: its agent, folder, and ID.
param($Agent, $When)
$list = Join-Path $HOME '.agent-sessions\open'
if ($env:GROK_HOOK_EVENT) { $Agent = 'grok' }   # Grok also runs Claude Code's hooks
$hook = [Console]::In.ReadToEnd() | ConvertFrom-Json
$id = if ($hook.session_id) { $hook.session_id } else { $hook.sessionId }
if (-not $id -or $id -match '[\\/]') { exit }
# Remove a session only when you quit it; a restart or a closed window keeps it.
$quit = ($Agent -eq 'claude' -and $hook.reason -in 'prompt_input_exit', 'clear', 'resume', 'logout') -or $Agent -eq 'codex'
if ($When -eq 'start') {
  New-Item -ItemType Directory -Force $list | Out-Null
  Set-Content (Join-Path $list $id) "$Agent`t$($hook.cwd)`t$id" -Encoding UTF8
} elseif ($quit) {
  Remove-Item (Join-Path $list $id) -ErrorAction SilentlyContinue
}
Enter fullscreen mode Exit fullscreen mode

The config files sit in the same places under your user folder. In each hook, the command runs the script through PowerShell. For Codex, in %USERPROFILE%\.codex\hooks.json, with your own user folder in the path:

{
  "hooks": {
    "SessionStart": [{ "hooks": [{ "type": "command", "command": "powershell -NoProfile -ExecutionPolicy Bypass -File \"C:/Users/you/.agent-sessions/track.ps1\" codex start" }] }],
    "SessionEnd": [{ "hooks": [{ "type": "command", "command": "powershell -NoProfile -ExecutionPolicy Bypass -File \"C:/Users/you/.agent-sessions/track.ps1\" codex end" }] }]
  }
}
Enter fullscreen mode Exit fullscreen mode

Use claude or grok in place of codex in the other two files. Keep the escaped quotes around the path: they hold it together when your user folder has a space in its name. Write the path with forward slashes. Codex and Grok run hook commands in PowerShell, Claude Code can run them in Git Bash, and forward slashes work in both.

How do I reopen them all at once?

After the restart, move the list aside first. The sessions you resume add themselves to a fresh list as they start, ready for the restart after this one. A resumed Codex session goes back on the list once you send it a message.

On macOS or Linux, print one line per session and paste each into its own tab:

rm -rf ~/.agent-sessions/last && mv ~/.agent-sessions/open ~/.agent-sessions/last
cat ~/.agent-sessions/last/* | while IFS="$(printf '\t')" read -r agent dir id; do
  case $agent in
    codex) cmd="codex resume $id" ;;
    *)     cmd="$agent --resume $id" ;;
  esac
  printf "cd '%s' && %s\n" "$(printf '%s' "$dir" | sed "s/'/'\\\\''/g")" "$cmd"
done
Enter fullscreen mode Exit fullscreen mode

If you work in tmux, let it open the windows. Run this inside a tmux session:

cat ~/.agent-sessions/last/* | while IFS="$(printf '\t')" read -r agent dir id; do
  case $agent in
    codex) cmd="codex resume $id" ;;
    *)     cmd="$agent --resume $id" ;;
  esac
  tmux new-window -c "$dir" -n "$agent" "$cmd; exec \$SHELL"
done
Enter fullscreen mode Exit fullscreen mode

On Windows, one Windows Terminal window opens with a tab per session, each in its folder:

Remove-Item -Recurse -Force ~\.agent-sessions\last -ErrorAction SilentlyContinue
Move-Item ~\.agent-sessions\open ~\.agent-sessions\last
$tabs = Get-ChildItem ~\.agent-sessions\last | Get-Content | ForEach-Object {
  $agent, $dir, $id = $_ -split "`t"
  $cmd = if ($agent -eq 'codex') { "codex resume $id" } else { "$agent --resume $id" }
  "new-tab -d `"$dir`" --title $agent powershell -NoExit -Command $cmd"
}
Start-Process wt -ArgumentList ("-w new " + ($tabs -join ' ; '))
Enter fullscreen mode Exit fullscreen mode

A session where you never sent a message has nothing to resume. Claude Code says No conversation found for it; close that tab.

Can PonyMux bring the group back for me?

In PonyMux, each Terminal is a saved record: its name, folder, List, and the sessions that ran in it. That is the list from the previous sections, kept for Claude Code, Codex, and Grok without a script of your own.

Quitting PonyMux or restarting the computer ends the processes, but the Terminals stay where you left them. The next time you open PonyMux, the agents that were still running wait in Agents under a Suspended heading, dimmed and marked with a zzz badge.

  • The whole group: click Wake All on the heading. PonyMux resumes each session in its own folder, one at a time. The heading shows the progress, such as 2 of 4, and Stop ends the run. Terminal → Wake Suspended Agents does the same.
  • One session: select it. PonyMux shows the last exchange, so you can see where it stopped, and resumes it after a short countdown.
  • A few of them: select several Terminals and choose Wake … Sleeping in the Inspector. They wake in the preview, side by side.

PonyMux after a relaunch: one agent running, and four Terminals dimmed under a Suspended heading with a Wake All button.

Wake All halfway through: two sessions are running again, and the heading reads 3 of 4 with a Stop button.

The guide on ponymux.com has this as a live demo you can click.

It works the same on Mac and Windows. PonyMux can only resume a session whose transcript is still on disk; after Claude Code's 30-day cleanup, for example, the Terminal says so and offers a plain shell. Sessions you started in another terminal app aren't PonyMux Terminals, so use the steps above for those.

For how sleeping and waking work, see Terminal Lifecycle. For the multi-select preview, see Preview Several Terminals. To find an older session by what was said in it, see Claude Code conversation history.

PonyMux is free for Mac and Windows: download it here.

Top comments (0)