How to bulk delete composer and chat history?

Is there a way to bulk delete?
Right now I have to click the trash bin button one by one and it will take a long time to clear everything.

Hey, unfortunately, there’s no other way at the moment, but you can rename or move your project, and then all the history will disappear.

Basta você renomear a pasta do seu projeto para qualquer nome e então os chatos irá apagar sozinho depois de alguns dias o cursos exclui eles

So when I renamed it and changed the project name back within a few minutes, the chat history still existed. Only after some time did the original chat history get deleted. My question is, how long do I have to wait specifically?

Hey, you don’t need to revert to the previous name in this case. If the name of your project is important to you, you can move it to another location on your computer, or delete the entire history manually, or reinstall Cursor.

How to delete entire history manually? I know there is a delete feature on the chat window but the ide hangs up before I can open it. Thanks.

You can delete the history only in the chat window, or you can also use the method I mentioned above.

Actually this happens so much, I wrote a cript to automate deleting history…

#!/bin/bash

CURSOR_WORKSPACES=~/Library/Application\ Support/Cursor/User/workspaceStorage

# Function to display help
show_help() {
    echo "Usage: $0 [parameter]"
    echo "If parameter s a project name (len!=32)  - will search for the workspace id"
    echo "If parameter is a workspace id (len==32) - will rename state.vscdb to state.vscdb.bad"
    echo "If parameter is ALL it will list all workspaces (not clean anything)"
}

# Check if no parameters are provided
if [ $# -eq 0 ]; then
    show_help
    exit 0
fi

# Get the parameter
param=$1

if [[ "$param" ==  "ALL" ]]; then
    pushd "${CURSOR_WORKSPACES}" > /dev/null
    grep -r folder */workspace.json | sed -E 's/^([a-f0-9]{32}).*\/([^\/]+)"$/\1 -> \2/'
    popd > /dev/null
else 
    if [ ${#param} -eq 32 ]; then
        # Rename the file
        if [ -f "${CURSOR_WORKSPACES}/${param}/state.vscdb" ]; then
            mv "${CURSOR_WORKSPACES}/${param}/state.vscdb" "${CURSOR_WORKSPACES}/${param}/state.vscdb.bad"
            echo "Renamed state.vscdb to state.vscdb.bad in ${param}"
        else
            echo "Error: state.vscdb not found in ${param}"
            exit 1
        fi
    else
        # Search for the parameter
        pushd "${CURSOR_WORKSPACES}" > /dev/null
        grep -r "/$param\"$" */workspace.json | sed -E 's/^([a-f0-9]{32}).*\/([^\/]+)"$/\1 -> \2/'
        popd > /dev/null
    fi
fi```

I used this script. Worked for me, please use with extreme caution.

#!/usr/bin/env python3
"""
Clean old Cursor chat history
"""
import os
import json
import argparse
from pathlib import Path
from datetime import datetime, timedelta
import urllib.parse
import shutil

def get_cursor_workspaces_dir():
    """Get the Cursor workspaces directory based on OS"""
    if os.name == 'nt':  # Windows
        return Path(os.environ['APPDATA']) / 'Cursor' / 'User' / 'workspaceStorage'
    else:  # macOS/Linux
        return Path.home() / 'Library' / 'Application Support' / 'Cursor' / 'User' / 'workspaceStorage'

def get_workspace_info(workspace_dir):
    """Extract information about a workspace"""
    info = {
        'workspace_id': workspace_dir.name,
        'project_name': 'Unknown',
        'project_path': 'Unknown',
        'state_file': None,
        'last_modified': None,
        'size_kb': 0,
        'age_days': None
    }
    
    # Get project info from workspace.json
    workspace_json = workspace_dir / 'workspace.json'
    if workspace_json.exists():
        try:
            with open(workspace_json, 'r', encoding='utf-8') as f:
                data = json.load(f)
                if 'folder' in data:
                    folder = data['folder']
                    # Remove file:/// prefix and decode URL encoding
                    folder = folder.replace('file:///', '')
                    folder = urllib.parse.unquote(folder)
                    info['project_path'] = folder
                    info['project_name'] = os.path.basename(folder)
        except Exception:
            pass
    
    # Get state.vscdb info
    state_file = workspace_dir / 'state.vscdb'
    if state_file.exists():
        stat = state_file.stat()
        info['state_file'] = state_file
        info['last_modified'] = datetime.fromtimestamp(stat.st_mtime)
        info['size_kb'] = round(stat.st_size / 1024, 2)
        info['age_days'] = (datetime.now() - info['last_modified']).days
    
    return info

def clean_old_history(days_old=7, dry_run=False, delete=False):
    """Clean chat history older than specified days"""
    workspaces_dir = get_cursor_workspaces_dir()
    
    if not workspaces_dir.exists():
        print(f"Error: Cursor workspace directory not found at: {workspaces_dir}")
        return
    
    cutoff_date = datetime.now() - timedelta(days=days_old)
    
    print(f"Searching for chat history older than {days_old} days (before {cutoff_date.strftime('%Y-%m-%d %H:%M:%S')})...")
    print()
    
    processed_count = 0
    skipped_count = 0
    
    # Scan all workspaces
    for workspace_dir in workspaces_dir.iterdir():
        if not workspace_dir.is_dir():
            continue
        
        info = get_workspace_info(workspace_dir)
        
        if not info['state_file']:
            continue
        
        if info['last_modified'] < cutoff_date:
            print(f"\033[93mFound: {info['project_name']}\033[0m")
            print(f"  Workspace ID: {info['workspace_id']}")
            print(f"  Last Modified: {info['last_modified'].strftime('%Y-%m-%d %H:%M:%S')}")
            print(f"  Age: {info['age_days']} days")
            
            if dry_run:
                action = 'deleted' if delete else 'renamed to state.vscdb.old'
                print(f"  \033[95mAction: [DRY RUN] Would be {action}\033[0m")
                processed_count += 1
            else:
                try:
                    if delete:
                        info['state_file'].unlink()
                        print(f"  \033[92mAction: Deleted\033[0m")
                    else:
                        backup_file = info['state_file'].with_suffix('.vscdb.old')
                        # If backup exists, append timestamp
                        if backup_file.exists():
                            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                            backup_file = info['state_file'].with_suffix(f'.vscdb.old.{timestamp}')
                        
                        shutil.move(str(info['state_file']), str(backup_file))
                        print(f"  \033[92mAction: Renamed to {backup_file.name}\033[0m")
                    
                    processed_count += 1
                except Exception as e:
                    print(f"  \033[91mAction: Failed - {e}\033[0m")
            
            print()
        else:
            skipped_count += 1
    
    # Summary
    print("=" * 60)
    if dry_run:
        print("\033[95mDRY RUN COMPLETE\033[0m")
        print(f"Would process: {processed_count} workspace(s)")
    else:
        print("\033[92mCOMPLETE\033[0m")
        print(f"Processed: {processed_count} workspace(s)")
    print(f"Skipped (newer): {skipped_count} workspace(s)")
    print("=" * 60)

def main():
    parser = argparse.ArgumentParser(
        description='Clean old Cursor chat history',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python clean_old_chat_history.py --dry-run              # Preview what would be changed
  python clean_old_chat_history.py                        # Rename history older than 7 days
  python clean_old_chat_history.py --days-old 14          # Rename history older than 14 days
  python clean_old_chat_history.py --delete               # Permanently delete old files
        """
    )
    
    parser.add_argument('--days-old', '-d', type=int, default=7,
                        help='Delete history older than this many days (default: 7)')
    parser.add_argument('--dry-run', '-n', action='store_true',
                        help='Show what would be deleted without actually deleting')
    parser.add_argument('--delete', action='store_true',
                        help='Permanently delete files (default: rename to .old)')
    
    args = parser.parse_args()
    
    clean_old_history(args.days_old, args.dry_run, args.delete)

if __name__ == '__main__':
    main()