Restore commits to their pre-squash state. Use when: squash grouping was wrong, need original commit history back, or want to try different grouping.
Restores the original commits from before the last squash operation. Works as long as backups exist (preserved until next /start-work or explicit cleanup).
Check in order of preference:
BRANCH=$(git branch --show-current)
SANITIZED_BRANCH=$(echo "$BRANCH" | tr '/' '-')
SESSION_DIR=".claude/sessions/${SANITIZED_BRANCH}"
if [ -f "$SESSION_DIR/last-squash.json" ]; then
BACKUP_TAG=$(jq -r .backupTag "$SESSION_DIR/last-squash.json")
ORIGINAL_HEAD=$(jq -r .originalHead "$SESSION_DIR/last-squash.json")
BUNDLE_PATH=$(jq -r .bundlePath "$SESSION_DIR/last-squash.json")
SQUASH_TIME=$(jq -r .squashedAt "$SESSION_DIR/last-squash.json")
echo "Found squash from: $SQUASH_TIME"
echo "Original HEAD: $ORIGINAL_HEAD"
fi
BACKUP_TAG=$(git tag -l "_squash-backup-*" | sort | tail -1)
if [ -n "$BACKUP_TAG" ]; then
echo "Found backup tag: $BACKUP_TAG"
fi
if [ -f "$SESSION_DIR/pre-squash.bundle" ]; then
echo "Found backup bundle"
fi
# If all else fails, check reflog
git reflog | grep -E "reset.*moving to" | head -5
echo "Manual recovery possible via reflog"
UPSTREAM=$(git rev-parse --abbrev-ref @{u} 2>/dev/null)
if [ -n "$UPSTREAM" ]; then
# Check if current HEAD matches remote
LOCAL_HEAD=$(git rev-parse HEAD)
REMOTE_HEAD=$(git rev-parse "$UPSTREAM" 2>/dev/null)
if [ "$LOCAL_HEAD" = "$REMOTE_HEAD" ]; then
echo "Warning: Squashed commits already pushed."
echo "Undo would require force push."
echo "Continue anyway? [y/n]"
# Only proceed if user explicitly confirms
fi
fi
if [ -n "$BACKUP_TAG" ] && git rev-parse "$BACKUP_TAG" >/dev/null 2>&1; then
echo "Restoring from backup tag: $BACKUP_TAG"
# Get the SHA before reset for confirmation
RESTORE_SHA=$(git rev-parse "$BACKUP_TAG")
COMMIT_COUNT=$(git log HEAD.."$BACKUP_TAG" --oneline | wc -l | tr -d " ")
echo "Will restore $COMMIT_COUNT commits"
echo "Target: $RESTORE_SHA"
git reset --hard "$BACKUP_TAG"
echo "Restored to: $(git log -1 --oneline)"
fi
if [ -f "$BUNDLE_PATH" ] && [ -n "$ORIGINAL_HEAD" ]; then
echo "Restoring from bundle..."
# Unbundle to make commits available
git bundle unbundle "$BUNDLE_PATH"
# Reset to original HEAD
git reset --hard "$ORIGINAL_HEAD"
echo "Restored to: $(git log -1 --oneline)"
fi
# Show reflog entries for user to choose
git reflog | head -20
echo "Enter the SHA or reflog entry to restore to:"
# User provides SHA like "HEAD@{2}" or "abc123"
# git reset --hard <user-provided-sha>
# Remove backup tag (used)
if [ -n "$BACKUP_TAG" ]; then
git tag -d "$BACKUP_TAG" 2>/dev/null
fi
# Remove backup files
rm -f "$SESSION_DIR/pre-squash.bundle"
rm -f "$SESSION_DIR/last-squash.json"
rm -f "$SESSION_DIR/squash-in-progress.json"
echo "Undo complete. Backup artifacts cleaned up."
Squash undone successfully!
Restored: 15 original commits
HEAD now at: abc123 "feat: original commit message"
Backup artifacts: cleaned up
You can now:
- Run /squash-commits again with different grouping
- Continue working and make more commits
- Push the original commits as-is
If no backup tag or bundle exists:
git reflog | head -20reset: moving to _session-start-*git reset --hard <sha>The tag might point to a garbage-collected commit:
ls -la $SESSION_DIR/*.bundlegit bundle unbundle <bundle>If you really need to undo after pushing:
/undo-squashgit push --force-with-lease