Skip to content

feat: add resume command for cross-slot session picker#106

Open
Andres-Briones wants to merge 5 commits into
RchGrav:mainfrom
Andres-Briones:feature/resume-command
Open

feat: add resume command for cross-slot session picker#106
Andres-Briones wants to merge 5 commits into
RchGrav:mainfrom
Andres-Briones:feature/resume-command

Conversation

@Andres-Briones
Copy link
Copy Markdown

@Andres-Briones Andres-Briones commented Apr 16, 2026

Summary

Adds claudebox resume — an interactive session picker (via fzf) that lets users resume any session from any slot without copy-pasting session IDs.

The problem

Claudebox isolates ~/.claude/ per slot. When a slot switches, /resume only sees sessions from the current slot — making sessions from other slots invisible. Users have to manually find and copy .jsonl files between slots.

How it works

The command runs entirely on the host:

  1. Scans all slots across all projects under ~/.claudebox/projects/ using the CRC32 chain to enumerate slot directories
  2. Extracts session titles (custom-title from .jsonl) and descriptions (first user message from history.jsonl)
  3. Detects session status by checking running containers (docker ps) and verifying active PIDs (docker exec kill -0)
  4. Presents an fzf picker with date, size, status, project, slot number, and description
  5. Resumes via run_claudebox_container in the correct slot

Session statuses

Status Meaning Action
ACTIVE (red) Session PID is alive in a running container Blocked — user should use the running container
busy (yellow) Slot's container is running a different session Copies .jsonl to an idle slot, resumes there
idle (dim) Slot's container is not running Starts the slot with --resume

Usage

claudebox resume          # current project sessions
claudebox resume -A       # sessions from all projects
claudebox resume -a       # no session limit
claudebox resume -n 20    # last 20 sessions

Requirements

  • fzf — fuzzy finder for interactive selection
  • jq — JSON processing for session metadata

Changes

  • New file: lib/commands.resume.sh_cmd_resume() implementation
  • Modified: lib/cli.sh — added resume to SCRIPT_COMMANDS and "none" requirements
  • Modified: lib/commands.sh — source, dispatch, and help text

Design decisions

  • Command requirement is "none" (pure host command) — it only needs Docker at the end when launching the container
  • Uses run_claudebox_container directly (same pattern as _cmd_slot)
  • All helpers prefixed with _resume_ to avoid namespace collisions
  • Uses if/then (not &&) per the project's set -e conventions
  • Cross-platform: handles both Linux and macOS stat/date differences

Test plan

  • Run claudebox resume from a project directory with multiple slots
  • Verify sessions from all slots appear with correct status indicators
  • Select an idle session → verify it launches in the correct slot
  • Select an active session → verify warning is shown
  • Test -A flag shows sessions from multiple projects
  • Test on macOS (BSD stat/date compatibility)

Summary by Sourcery

Add a host-only interactive resume command that lets users pick and resume sessions across slots and projects via an fzf-based picker.

New Features:

  • Introduce a claudebox resume command that enumerates sessions across slots and projects and resumes the selected one.
  • Add an interactive fzf-based session picker showing metadata such as date, size, status, project, slot, and description.

Enhancements:

  • Integrate the resume command into the CLI command list, help output, and command dispatcher as a pure host-side operation.

Interactive fzf-based session picker that scans all slots, detects
active sessions via PID checks, and resumes in the correct slot.
Handles busy slots by copying the session to an idle slot.
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 16, 2026

Reviewer's Guide

Implements a new host-only claudebox resume command that scans all project slots for session JSONL files, enriches them with metadata (titles, descriptions, status), presents them via an fzf-based picker, and resumes the selected session in the appropriate or an idle slot, wiring the command into the existing CLI parsing, dispatch, and help systems.

Flow diagram for _cmd_resume logic and status handling

flowchart TD
    A_start[Start_claudebox_resume] --> B_parse
    B_parse[Parse_flags_-n_-a_-A_-h] --> C_deps
    C_deps[Check_dependencies_fzf_jq_docker] -->|missing| Z_err_no_deps[Print_error_and_exit]
    C_deps -->|ok| D_scan_root

    D_scan_root[Locate_projects_dir_~/.claudebox/projects] -->|not_found| Z_no_projects[Print_no_projects_and_exit]
    D_scan_root -->|found| E_validate_project

    E_validate_project[Validate_current_project_unless_-A] -->|invalid| Z_not_in_project[Print_use_-A_and_exit]
    E_validate_project -->|ok| F_build_desc

    F_build_desc[Scan_history_jsonl_for_descriptions] --> G_scan_sessions

    G_scan_sessions[Enumerate_projects_and_slots_via_crc32_chain] --> H_scan_slot_ws
    H_scan_slot_ws[Scan_workspace_jsonl_files] --> I_filter_and_collect

    I_filter_and_collect[Filter_small_files_and_subagents_collect_metadata] --> J_status

    J_status[Determine_running_and_active_status] --> K_build_lists
    K_build_lists[Write_sessions_titles_descriptions_TSV] --> L_limit

    L_limit[Sort_by_mtime_dedup_sessionIds_apply_limit_or_all] -->|no_sessions| Z_no_sessions[Print_no_sessions_and_exit]
    L_limit -->|has_sessions| M_build_fzf_input

    M_build_fzf_input[Format_fzf_rows_with_colors_and_status] -->|empty| Z_no_sessions
    M_build_fzf_input --> N_run_fzf

    N_run_fzf[Invoke_fzf_picker] -->|esc_or_none| Z_exit_ok[Exit_success_no_selection]
    N_run_fzf -->|selection| O_extract

    O_extract[Extract_sessionId_slot_hash_slot_idx_project_path_status] --> P_status_branch

    P_status_branch -->|ACTIVE| Q_active
    P_status_branch -->|RUNNING_busy| R_busy
    P_status_branch -->|IDLE| S_idle

    Q_active[Print_active_warning_do_not_resume] --> Z_active_exit[Exit_with_error]

    R_busy[Find_idle_slot_copy_session_jsonl] -->|no_idle| Z_no_idle[Print_no_idle_slots_and_exit]
    R_busy -->|idle_found| T_prepare_resume_busy[Update_slot_idx_to_idle]

    S_idle[Prepare_resume_in_selected_slot] --> T_prepare_resume_idle

    T_prepare_resume_busy --> U_env
    T_prepare_resume_idle --> U_env

    U_env[Set_PROJECT_env_and_container_names] --> V_run_container
    V_run_container[run_claudebox_container_interactive_--resume_sessionId] --> W_attach[User_attached_to_container]
    W_attach --> X_end[End]

    Z_err_no_deps --> X_end
    Z_no_projects --> X_end
    Z_not_in_project --> X_end
    Z_no_sessions --> X_end
    Z_exit_ok --> X_end
    Z_active_exit --> X_end
    Z_no_idle --> X_end
Loading

File-Level Changes

Change Details Files
Add a host-only resume CLI command that discovers sessions across projects/slots, surfaces metadata in an fzf picker, and resumes the selected session with appropriate slot/container handling.
  • Introduce _cmd_resume and a set of _resume_-prefixed helper functions to support cross-platform timestamp/size formatting, docker container status checks, session activity detection, and slot counter reading.
  • Scan ~/.claudebox/projects for project/slot directories, derive slot hashes via the existing CRC32-based generate_container_name, and enumerate -workspace/*.jsonl sessions while skipping small or subagent files.
  • Collect session titles from custom-title messages in session .jsonl and descriptions from history.jsonl (first user display), deduplicate by sessionId, and join this metadata with size, date, project, and slot info into an fzf input table with ANSI-colored status and description.
  • Determine session status by correlating Docker container names with slot hashes and, for running slots, checking active PIDs via docker exec kill -0, classifying sessions as ACTIVE, RUNNING (busy), or IDLE.
  • Handle the selected row from fzf by parsing hidden TSV fields, then either warning on ACTIVE sessions, copying the session JSONL to an idle slot for RUNNING slots, or directly resuming IDLE sessions by exporting the expected environment variables and invoking run_claudebox_container ... --resume <sessionId>.
  • Support flags -n (limit by recent sessions), -a (no limit), -A/--all-projects (scan all projects instead of current), -d/--debug, validate usage, and ensure proper cleanup of temporary files with traps.
lib/commands.resume.sh
Wire the new resume command into the existing CLI command list, dispatch, and help text as a pure host command.
  • Source lib/commands.resume.sh in the main commands aggregator and document the resume command in the RESUME COMMAND section.
  • Extend show_help output with a resume line describing it as an fzf-based cross-slot session resume command.
  • Add resume to SCRIPT_COMMANDS in lib/cli.sh so the centralized CLI parser recognizes it as a script-level command.
  • Mark resume as a none-requirement command in get_command_requirements, aligning it with other host-only commands that don’t require Docker/image setup until actual resume execution.
lib/commands.sh
lib/cli.sh

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • When -A is not used, _cmd_resume assumes PROJECT_DIR is set and valid for generate_parent_folder_name, but there’s no explicit guard for being outside a project directory; consider failing fast with a clear error if PROJECT_DIR is unset or invalid instead of relying on upstream state.
  • The CLI marks resume as having "none" requirements in get_command_requirements, but _cmd_resume depends on a working Docker daemon (for docker ps and docker exec); aligning the requirement classification with this behavior will keep the CLI’s expectations consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- When `-A` is not used, `_cmd_resume` assumes `PROJECT_DIR` is set and valid for `generate_parent_folder_name`, but there’s no explicit guard for being outside a project directory; consider failing fast with a clear error if `PROJECT_DIR` is unset or invalid instead of relying on upstream state.
- The CLI marks `resume` as having `"none"` requirements in `get_command_requirements`, but `_cmd_resume` depends on a working Docker daemon (for `docker ps` and `docker exec`); aligning the requirement classification with this behavior will keep the CLI’s expectations consistent.

## Individual Comments

### Comment 1
<location path="lib/commands.resume.sh" line_range="97" />
<code_context>
+
+    while [ $# -gt 0 ]; do
+        case "$1" in
+            -n) limit="$2"; shift 2 ;;
+            -a) show_all=true; shift ;;
+            -A|--all-projects) all_projects=true; shift ;;
</code_context>
<issue_to_address>
**issue:** Validate the numeric argument to -n before using it as a line limit.

If `-n` is passed without a value or with a non-numeric value, `limit` may be empty/invalid and `head -n "$limit"` will error or behave unexpectedly. Please validate that `$2` exists and is numeric (e.g. `^[0-9]+$`) before assigning, and exit with a clear error message otherwise.
</issue_to_address>

### Comment 2
<location path="lib/commands.resume.sh" line_range="386-387" />
<code_context>
+        local date_str size_h title desc display status status_display
+        date_str=$(_resume_format_date "$mtime")
+        size_h=$(_resume_human_size "$size")
+        title=$(grep "^${sid}	" "$titles_file" 2>/dev/null | head -1 | cut -f2- || true)
+        desc=$(grep "^${sid}	" "$desc_file" 2>/dev/null | head -1 | cut -f2- || true)
+
+        if [ -n "$title" ]; then
</code_context>
<issue_to_address>
**issue (bug_risk):** Quote or otherwise sanitize `sid` when using it in grep patterns.

Here `sid` is used as a regex. If it contains metacharacters (e.g. `.`, `[]`), the match may be wrong. To avoid this, either use `grep -F` for fixed-string matching or escape regex metacharacters in `sid` before building the pattern.
</issue_to_address>

### Comment 3
<location path="lib/commands.resume.sh" line_range="460-469" />
<code_context>
+            local resume_parent_dir="$projects_dir/$(generate_parent_folder_name "$project_path")"
</code_context>
<issue_to_address>
**issue (bug_risk):** Check that the source session file exists before copying in the RUNNING case.

In the RUNNING branch you construct `source_jsonl` and then always `cp` it into the idle slot. If that file was deleted or never created (e.g., manual cleanup, partial write), `cp` will fail and the resume will continue with an incomplete session. Add a check that `$source_jsonl` exists (and ideally is non-empty) before copying, and abort the resume with a clear error if it is missing or invalid.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread lib/commands.resume.sh Outdated
Comment thread lib/commands.resume.sh Outdated
Comment thread lib/commands.resume.sh
Claudebox passes control flags (e.g. --dangerously-skip-permissions)
to all command handlers. The resume parser now silently skips them
instead of erroring.
- Validate -n argument is numeric before using as limit
- Guard PROJECT_DIR when not using -A (clear error if outside project)
- Use grep -F for session ID lookups (avoid regex metachar issues)
- Check source .jsonl exists before copying in busy-slot case
@Andres-Briones
Copy link
Copy Markdown
Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • Consider breaking _cmd_resume into smaller helper functions (e.g., argument parsing, project/slot enumeration, session collection, fzf rendering, resume execution) to improve readability and maintainability of this ~500-line function.
  • You’re manually calling stat in multiple places to get file sizes and mtimes; it may be more robust and consistent to centralize the cross-platform stat logic into a shared helper (similar to _resume_get_mtime) and reuse it for size as well.
  • Since uname -s is called repeatedly in _resume_get_mtime and _resume_format_date, you could cache the OS type once in a variable to avoid redundant process invocations on every file processed.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider breaking `_cmd_resume` into smaller helper functions (e.g., argument parsing, project/slot enumeration, session collection, fzf rendering, resume execution) to improve readability and maintainability of this ~500-line function.
- You’re manually calling `stat` in multiple places to get file sizes and mtimes; it may be more robust and consistent to centralize the cross-platform `stat` logic into a shared helper (similar to `_resume_get_mtime`) and reuse it for size as well.
- Since `uname -s` is called repeatedly in `_resume_get_mtime` and `_resume_format_date`, you could cache the OS type once in a variable to avoid redundant process invocations on every file processed.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- Cache uname -s once in _RESUME_PLATFORM module variable
- Add _resume_get_size helper for cross-platform file size
- Extract _resume_build_descriptions for history.jsonl parsing
- Extract _resume_discover_sessions and _resume_scan_workspace
- Extract _resume_build_fzf_input for display line construction
- Extract _resume_execute for resume logic (active/busy/idle)
- _cmd_resume is now a thin orchestrator calling the phases

No behavioral changes — pure refactoring for readability.
Header at top, most recent sessions first, cursor starts on newest.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant