Skip to content

Commit b303372

Browse files
MrFlounderclaude
andcommitted
feat(wip): save and restore Claude sessions across WIP save/restore
Captures Claude Code session ID during wip save and resumes it on restore. Fixes _workspace_has_changes cwd leak and defaults restore to auto-relaunch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent df3fe83 commit b303372

1 file changed

Lines changed: 66 additions & 19 deletions

File tree

src/crabcode

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
set -e
2929

30-
VERSION="0.9.1" # x-release-please-version
30+
VERSION="0.10.0" # x-release-please-version
3131
CONFIG_DIR="$HOME/.crabcode"
3232
CONFIG_FILE="$CONFIG_DIR/config.yaml"
3333
WIP_BASE="$CONFIG_DIR/wip"
@@ -1430,6 +1430,16 @@ open_workspace() {
14301430
local dev_cmd=$(get_pane_command "server")
14311431
local claude_cmd=$(get_pane_command "main")
14321432

1433+
# Check for WIP Claude session resume
1434+
local resume_file="$dir/.claude-resume-session"
1435+
if [ -f "$resume_file" ] && [[ "$claude_cmd" == *"claude"* ]]; then
1436+
local resume_id=$(cat "$resume_file")
1437+
if [ -n "$resume_id" ]; then
1438+
claude_cmd="$claude_cmd --resume $resume_id"
1439+
rm -f "$resume_file"
1440+
fi
1441+
fi
1442+
14331443
# Always ensure team context exists in CLAUDE.md
14341444
local team_file="$dir/.claude/CLAUDE.md"
14351445
mkdir -p "$dir/.claude"
@@ -1922,8 +1932,17 @@ continue_workspace() {
19221932
local dev_cmd=$(get_pane_command "server")
19231933
local claude_cmd=$(get_pane_command "main")
19241934

1925-
# Add --continue to claude command if it contains "claude"
1926-
if [[ "$claude_cmd" == *"claude"* ]]; then
1935+
# Add session resume or --continue to claude command
1936+
local resume_file="$dir/.claude-resume-session"
1937+
if [ -f "$resume_file" ] && [[ "$claude_cmd" == *"claude"* ]]; then
1938+
local resume_id=$(cat "$resume_file")
1939+
if [ -n "$resume_id" ]; then
1940+
claude_cmd="$claude_cmd --resume $resume_id"
1941+
rm -f "$resume_file"
1942+
else
1943+
claude_cmd="$claude_cmd --continue"
1944+
fi
1945+
elif [[ "$claude_cmd" == *"claude"* ]]; then
19271946
claude_cmd="$claude_cmd --continue"
19281947
fi
19291948

@@ -2231,6 +2250,13 @@ wip_save() {
22312250
fi
22322251
done
22332252

2253+
# Capture Claude session ID for conversation resume
2254+
local claude_session=""
2255+
local claude_project_dir="$HOME/.claude/projects/$(echo "$dir" | tr '/.' '--')"
2256+
if [ -d "$claude_project_dir" ]; then
2257+
claude_session=$(ls -t "$claude_project_dir"/*.jsonl 2>/dev/null | head -1 | xargs -I{} basename {} .jsonl 2>/dev/null || echo "")
2258+
fi
2259+
22342260
cat > "$wip_path/metadata.json" << EOF
22352261
{
22362262
"timestamp": "$timestamp",
@@ -2239,6 +2265,7 @@ wip_save() {
22392265
"workspace": $num,
22402266
"branch": "$branch",
22412267
"commits_ahead": $commits_ahead,
2268+
"claude_session": "$claude_session",
22422269
"created_at": "$(date -Iseconds)"
22432270
}
22442271
EOF
@@ -2319,9 +2346,14 @@ wip_list() {
23192346
local display_date=$(echo "$created" | cut -d'T' -f1)
23202347
local display_time=$(echo "$created" | cut -d'T' -f2 | cut -d'+' -f1 | cut -d'-' -f1 | cut -c1-5)
23212348

2349+
# Check for Claude session
2350+
local claude_tag=""
2351+
local cs=$(grep -o '"claude_session"[[:space:]]*:[[:space:]]*"[^"]*"' "$metadata" | sed 's/"claude_session"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
2352+
[ -n "$cs" ] && claude_tag=" ${CYAN}Claude: saved${NC}"
2353+
23222354
echo -e " ${GREEN}[$i]${NC} $name"
23232355
echo -e " ${BLUE}$summary${NC}"
2324-
echo -e " Branch: $branch | Created: $display_date $display_time"
2356+
echo -e " Branch: $branch | Created: $display_date $display_time${claude_tag}"
23252357
echo ""
23262358
else
23272359
echo -e " ${GREEN}[$i]${NC} $name (no metadata)"
@@ -2411,7 +2443,12 @@ wip_list_global() {
24112443

24122444
echo -e " ${GREEN}[$i]${NC} ${BOLD}$wip_name${NC}"
24132445
echo -e " ${BLUE}$summary${NC}"
2414-
echo -e " ${GRAY}Workspace: ${NC}$ws_num ${GRAY}Branch: ${NC}$branch ${GRAY}Files: ${NC}$file_count patches"
2446+
# Check for Claude session
2447+
local claude_tag=""
2448+
local claude_session=$(grep -o '"claude_session"[[:space:]]*:[[:space:]]*"[^"]*"' "$metadata" | sed 's/"claude_session"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
2449+
[ -n "$claude_session" ] && claude_tag=" ${CYAN}Claude: saved${NC}"
2450+
2451+
echo -e " ${GRAY}Workspace: ${NC}$ws_num ${GRAY}Branch: ${NC}$branch ${GRAY}Files: ${NC}$file_count patches${claude_tag}"
24152452
if [ -n "$commits_ahead" ] && [ "$commits_ahead" != "0" ]; then
24162453
echo -e " ${GRAY}Commits ahead: ${NC}$commits_ahead ${GRAY}Created: ${NC}$display_date $display_time"
24172454
else
@@ -2564,13 +2601,13 @@ wip_restore_global() {
25642601
_workspace_has_changes() {
25652602
local dir="$1"
25662603
[ ! -d "$dir" ] && return 1
2567-
cd "$dir"
25682604
# Check for any changes (staged, unstaged, or untracked)
2569-
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
2605+
# Use git -C to avoid changing the caller's working directory
2606+
if ! git -C "$dir" diff --quiet 2>/dev/null || ! git -C "$dir" diff --cached --quiet 2>/dev/null; then
25702607
return 0
25712608
fi
25722609
# Check for untracked files
2573-
if [ -n "$(git ls-files --others --exclude-standard 2>/dev/null)" ]; then
2610+
if [ -n "$(git -C "$dir" ls-files --others --exclude-standard 2>/dev/null)" ]; then
25742611
return 0
25752612
fi
25762613
return 1
@@ -2896,17 +2933,27 @@ _restore_wip() {
28962933

28972934
success "WIP restored: $wip_name"
28982935

2899-
# Check if user was already in the workspace when they started
2900-
if [[ "$original_dir" == "$dir"* ]]; then
2901-
# Already in workspace, no need to open it
2902-
:
2903-
elif [ "$open_after" = "true" ]; then
2936+
# Write Claude session resume file if available
2937+
if [ -f "$metadata" ]; then
2938+
local claude_session=$(grep -o '"claude_session"[[:space:]]*:[[:space:]]*"[^"]*"' "$metadata" | sed 's/"claude_session"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
2939+
if [ -n "$claude_session" ] && [ "$claude_session" != "" ]; then
2940+
echo "$claude_session" > "$dir/.claude-resume-session"
2941+
echo -e " ${BLUE}Claude conversation will resume automatically${NC}"
2942+
fi
2943+
fi
2944+
2945+
# Relaunch the workspace tmux window with Claude session resume
2946+
if [ "$open_after" = "true" ]; then
29042947
echo ""
2905-
echo " Opening workspace $num..."
2906-
open_workspace "$num"
2948+
echo " Relaunching workspace $num..."
2949+
continue_workspace "$num"
29072950
else
29082951
echo ""
2909-
echo " Run 'crab $num' to open the workspace"
2952+
if [ -f "$dir/.claude-resume-session" ]; then
2953+
echo -e " Run ${BOLD}crab $num continue${NC} to launch with the saved Claude conversation"
2954+
else
2955+
echo -e " Run 'crab $num' to open the workspace"
2956+
fi
29102957
fi
29112958
}
29122959

@@ -9164,11 +9211,11 @@ main() {
91649211
fi
91659212
;;
91669213
"restore")
9167-
# Restore by index with optional target workspace and --open flag
9168-
# Usage: crab wip restore [<N>] [--to <ws>] [--open]
9214+
# Restore by index with optional target workspace
9215+
# Usage: crab wip restore [<N>] [--to <ws>]
91699216
local target_ws=""
91709217
local index=""
9171-
local open_after="false"
9218+
local open_after="true"
91729219
shift 2 # Remove "wip" and "restore"
91739220

91749221
# Parse arguments

0 commit comments

Comments
 (0)