Skip to content

feat: add direct session resume by name#181

Open
ccrios83 wants to merge 3 commits intompfaffenberger:mainfrom
ccrios83:feature/direct-session-resume
Open

feat: add direct session resume by name#181
ccrios83 wants to merge 3 commits intompfaffenberger:mainfrom
ccrios83:feature/direct-session-resume

Conversation

@ccrios83
Copy link
Copy Markdown
Contributor

@ccrios83 ccrios83 commented Feb 16, 2026

Summary

Allow users to resume a session directly by name instead of always opening the interactive picker:

/resume                              # Opens picker (unchanged)
/resume auto_session_20260214_175543 # Loads directly (NEW)

Problem

Users kept getting told they could use /resume <session_name> but it didn't actually work - it just opened the picker regardless.

Changes

File Changes
session_commands.py Parse optional session_name argument, return direct load marker
cli_runner.py Handle __AUTOSAVE_LOAD_DIRECT__ marker with session lookup

User Experience

Success:

✅ Resumed: auto_session_20260214_175543
📊 47 messages (12,345 tokens)

Error:

❌ Session not found: invalid_session_name
💡 Use /resume to open the session picker.

Testing

  • All existing session tests pass ✅
  • Manual testing confirms both picker and direct load work

Ported from Walmart internal implementation

Summary by CodeRabbit

  • Improvements
    • Session loading now supports direct load by session name in addition to interactive selection.
    • Enhanced error handling for session loading with helpful error messages and recovery guidance.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

@ccrios83 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Introduces a new direct-load path for autosave sessions triggered by "AUTOSAVE_LOAD_DIRECT:" commands, complementing the existing interactive picker. Extracts session names, loads session history, updates agent message history, and computes token counts with error handling that gracefully falls back to interactive flow.

Changes

Cohort / File(s) Summary
Autosave Direct-Load Implementation
code_puppy/cli_runner.py
Adds direct-load logic for "AUTOSAVE_LOAD_DIRECT:" prefixed commands that extracts session names, loads session files, updates agent history, and emits success/error messages with metadata. Maintains precedence over interactive flow.
Autosave Load Command Enhancement
code_puppy/command_line/session_commands.py
Extends handle_autosave_load_command to parse tokens and return either "AUTOSAVE_LOAD" (interactive) or "AUTOSAVE_LOAD_DIRECT:<session_name>" (direct). Updates decorator metadata with new description, usage pattern, and detailed help documentation.

Sequence Diagram

sequenceDiagram
    participant User
    participant CommandHandler as Command Handler
    participant CLIRunner as CLI Runner
    participant SessionFile as Session File System

    User->>CommandHandler: /autosave_load [session_name]
    CommandHandler->>CommandHandler: Parse tokens
    alt Session name provided
        CommandHandler->>CLIRunner: Return "__AUTOSAVE_LOAD_DIRECT__:<session_name>"
        CLIRunner->>SessionFile: Check session file exists
        SessionFile->>CLIRunner: File status
        CLIRunner->>SessionFile: Load session history
        SessionFile->>CLIRunner: History data
        CLIRunner->>CLIRunner: Update agent message history<br/>Compute token count
        CLIRunner->>User: Emit success with metadata
    else No session name
        CommandHandler->>CLIRunner: Return "__AUTOSAVE_LOAD__"
        CLIRunner->>User: Trigger interactive session picker
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A direct path to memories past,
No longer waiting for prompts to ask,
Sessions now load with a name so clear,
While fallback flows keep us without fear.
Quick hops and bounces through autosave land! 🌟

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding direct session resume capability by name, which is the core change across both modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@code_puppy/cli_runner.py`:
- Around line 661-668: session_name is used directly to build session_path
allowing path traversal (e.g. "../../foo"); instead of interpolating blindly,
resolve the candidate path and verify it is inside AUTOSAVE_DIR before checking
exists() or deserializing: build base_dir = Path(AUTOSAVE_DIR).resolve(),
construct session_path = (base_dir / session_name).resolve() (or otherwise join
then resolve), then ensure session_path == base_dir or base_dir in
session_path.parents; if the check fails, call emit_error/emit_info and
continue; perform this guard prior to the exists() check and any unpickling to
prevent access outside AUTOSAVE_DIR.
🧹 Nitpick comments (3)
code_puppy/command_line/session_commands.py (1)

203-217: Return type annotation -> bool is incorrect — function returns str.

handle_autosave_load_command is annotated as returning bool, but it actually returns string markers ("__AUTOSAVE_LOAD__" or "__AUTOSAVE_LOAD_DIRECT__:…"). This was likely pre-existing for the __AUTOSAVE_LOAD__ case, but now with two distinct string return paths it's worth correcting.

Proposed fix
-def handle_autosave_load_command(command: str) -> bool:
+def handle_autosave_load_command(command: str) -> str:
code_puppy/cli_runner.py (1)

650-692: Consider extracting shared session-load logic into a helper.

The direct-load block (lines 650-692) and the interactive-picker block (lines 694-761) share nearly identical steps: load_sessionset_message_historyset_current_autosave_from_session_name → token counting → success message. Extracting a small helper (e.g., _apply_loaded_session(session_name, base_dir, agent)) would reduce duplication and keep both paths in sync.

code_puppy/plugins/customizable_commands/register_callbacks.py (1)

54-54: Fragile string comparison to select glob pattern.

The *.prompt.md pattern is selected via directory != ".github/prompts", which is a brittle coupling to the exact string in _COMMAND_DIRECTORIES. If someone normalizes or changes that entry, this silently breaks.

Consider a small improvement:

Proposed fix
-        pattern = "*.md" if directory != ".github/prompts" else "*.prompt.md"
+        pattern = "*.prompt.md" if "prompts" in Path(directory).parts else "*.md"

Or store tuples of (directory, pattern) in _COMMAND_DIRECTORIES.

Comment thread code_puppy/cli_runner.py Outdated
Allow users to resume a session directly by name instead of
always opening the interactive picker:

  /resume                              # Opens picker (unchanged)
  /resume auto_session_20260214_175543 # Loads directly (NEW)

- session_commands.py: Parse optional session_name argument
- cli_runner.py: Handle __AUTOSAVE_LOAD_DIRECT__ marker

Closes: user feedback about /resume <name> not working
@ccrios83 ccrios83 force-pushed the feature/direct-session-resume branch from 680a89e to 541c60a Compare February 16, 2026 18:38
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
code_puppy/command_line/session_commands.py (1)

203-217: Return type annotation says bool but function returns str.

handle_autosave_load_command is annotated -> bool but returns string markers on both branches. This predates this PR (the __AUTOSAVE_LOAD__ return existed before), but the PR extends the pattern. Consider updating the annotation to -> Union[bool, str] (or bool | str) so type checkers and readers aren't misled.

Also, extra tokens after the session name are silently ignored (e.g., /resume foo bar uses "foo" and drops "bar"). If that's intentional, a brief comment would help; otherwise you could warn the user or join the tail.

Proposed annotation fix and extra-token handling
-def handle_autosave_load_command(command: str) -> bool:
+def handle_autosave_load_command(command: str) -> "bool | str":
     """Load an autosave session.
 
     If no session name is provided, opens interactive picker.
     If session name is provided, loads that session directly.
     """
     tokens = command.split()
 
     if len(tokens) == 1:
         # No argument = open picker
         return "__AUTOSAVE_LOAD__"
 
+    if len(tokens) > 2:
+        from code_puppy.messaging import emit_warning
+        emit_warning("Usage: /resume [session_name] — extra arguments ignored.")
+
     # Session name provided = load directly
     session_name = tokens[1]
     return f"__AUTOSAVE_LOAD_DIRECT__:{session_name}"
code_puppy/cli_runner.py (4)

656-666: Redundant manual existence check — load_session already raises FileNotFoundError.

Lines 657–663 manually construct session_path and check .exists(), but load_session (line 666) independently builds the path via build_session_paths and raises FileNotFoundError if it doesn't exist. Two concerns:

  1. Inconsistency risk: If build_session_paths ever diverges from base_dir / f"{session_name}.pkl", the manual check could pass while load_session fails (or vice versa).
  2. Redundancy: The except Exception block on line 682 already catches errors, so the manual check is purely for a friendlier error message — but you can achieve the same by catching FileNotFoundError specifically.
Proposed simplification — catch FileNotFoundError explicitly
                     try:
                         from code_puppy.agents.agent_manager import get_current_agent
                         from code_puppy.config import (
                             set_current_autosave_from_session_name,
                         )
                         from code_puppy.messaging import emit_error, emit_success
                         from code_puppy.session_storage import load_session
 
                         base_dir = Path(AUTOSAVE_DIR)
-                        session_path = base_dir / f"{session_name}.pkl"
-
-                        # Check if session exists
-                        if not session_path.exists():
-                            emit_error(f"❌ Session not found: {session_name}")
-                            emit_info("💡 Use /resume to open the session picker.")
-                            continue
-
-                        # Load the session
                         history = load_session(session_name, base_dir)
 
                         agent = get_current_agent()
                         agent.set_message_history(history)
                         # ...
+                    except FileNotFoundError:
+                        emit_error(f"❌ Session not found: {session_name}")
+                        emit_info("💡 Use /resume to open the session picker.")
                     except Exception as e:
-                        from code_puppy.messaging import emit_error
-
                         emit_error(f"Failed to load session: {e}")
                         emit_info("💡 Use /resume to open the session picker.")
                     continue

645-687: Significant code duplication between direct-load and interactive-picker paths.

The direct-load block (lines 648–687) and the interactive-picker block (lines 690–756) share nearly identical post-load logic:

  • load_session(name, base_dir)
  • agent.set_message_history(history)
  • set_current_autosave_from_session_name(name)
  • Token estimation loop
  • emit_success(...) with session stats

Consider extracting a helper (e.g., _apply_loaded_session(session_name, history, agent)) to eliminate this duplication. This would also make it easier to keep the success message format consistent between the two paths (the direct path uses {total_tokens:,} with a thousands separator while the interactive path uses {total_tokens} without one).

Sketch of extracted helper
def _apply_loaded_session(session_name: str, history, agent) -> str:
    """Apply loaded session history to agent and return summary string."""
    from code_puppy.config import set_current_autosave_from_session_name

    agent.set_message_history(history)
    set_current_autosave_from_session_name(session_name)
    total_tokens = sum(agent.estimate_tokens_for_message(msg) for msg in history)
    return f"{len(history)} messages ({total_tokens:,} tokens)"

Both the direct-load and interactive-picker blocks can then call this helper after load_session, keeping the logic DRY and the output format consistent.

Also applies to: 688-756


645-647: Marker-based control flow is fragile — consider using a structured return type.

The __AUTOSAVE_LOAD_DIRECT__: and __AUTOSAVE_LOAD__ magic string markers couple session_commands.py and cli_runner.py via undocumented string conventions. If a marker is typo'd or the delimiter changes, the bug will be silent. A lightweight dataclass or NamedTuple (e.g., CommandAction(kind="autosave_load_direct", payload=session_name)) would give you type safety and IDE autocompletion at negligible cost.

This is a broader pattern in the existing code, so no need to fix it in this PR, but worth considering for future refactors.


674-676: Token estimation iterates the full history eagerly.

sum(agent.estimate_tokens_for_message(msg) for msg in history) walks every message for a display-only stat. For large histories this could be slow. Not a blocker, but worth noting — if load_session metadata already stores a token count, you could read it from there instead.

Christopher Rios added 2 commits February 16, 2026 11:43
- Validate session_path stays within AUTOSAVE_DIR before loading
- Resolve paths to prevent ../../ escape attacks
- Fix return type annotation (bool -> bool | str)

Addresses CodeRabbit security review feedback
Copy link
Copy Markdown
Collaborator

@nhicks00 nhicks00 left a comment

Choose a reason for hiding this comment

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

Review: Request Changes

Thanks for adding direct session resume! Great feature. Found a few issues that need addressing:

🐛 Critical: emit_info Not Imported

In cli_runner.py, the direct load error path uses emit_info but only imports emit_error and emit_success:

from code_puppy.messaging import emit_error, emit_success  # ❌ Missing emit_info!

emit_error(f"❌ Invalid session name: {session_name}")
emit_info("💡 Use /resume to open the session picker.")  # 💥 NameError!

This will crash when a session is not found or path traversal is detected.

Fix: Add emit_info to the import statement.


⚠️ UX Inconsistency: Missing display_resumed_history

The interactive picker path calls display_resumed_history(history) after loading, but the direct load path skips this. Users get different experiences depending on how they resume.

Suggestion: Add the same display_resumed_history(history) call to the direct load path for consistency.


💡 Nice to Have: DRY Refactor

The session loading logic is duplicated between both paths:

  • load_session()
  • set_message_history()
  • set_current_autosave_from_session_name()
  • Token calculation
  • Success message

Consider extracting to a shared helper like _load_and_resume_session(session_name).


✅ What Looks Good

  • Path traversal protection 👍
  • Backward compatibility (no args = picker)
  • Updated help text and docstrings
  • All tests pass

Let me know if you have questions!

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.

2 participants