[composerModesService] Failed to register action for mode

Same issue…
Disable plan mode. It works great!!

Nice catch — that’s exactly my case! But how did you remove/rename your custom Plan mode if the mode list was empty because of the bug?

I found out that it was because my custom mode conflicts with a mode called plan and cursor, and the button switch of plan mode enable disappeared after the new version was updated; I could only solve this problem by finding the corresponding record in sqllite and modifying the database :joy:.

operates as follows:

  1. get the mode id

2.use the mode id, search in sqllite

  1. Find the corresponding custom mode in the configuration to modify the name or delete item
    note :warning:! The last step requires closing the cursor, otherwise it will fail

I was able to get Codex to help me do it. Here’s the rough prompt:

# Cursor Custom Mode Conflict Fix

## Background
- Cursor stores custom agent modes in `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb`.
- Sometimes custom modes conflict with built-in modes or cause issues like repeated "Failed to register action for mode …" errors.
- These problematic entries can respawn if Cursor is running while you edit the database, so perform the fix with the app completely closed.

## One-Time Preparation
1. **Quit Cursor** – ensure no `Cursor.app` processes are running. (On macOS you can confirm with `Activity Monitor` or `ps aux | grep Cursor`.)
2. **Back up the state database**:
   ```sh
   cp ~/Library/Application\ Support/Cursor/User/globalStorage/state.vscdb \
      ~/Library/Application\ Support/Cursor/User/globalStorage/state.vscdb.bak.$(date +%Y%m%d%H%M%S)
   ```

## Removal Steps

> **Note:** Common problematic mode IDs include legacy custom modes that conflict with Cursor's built-in modes. If you're seeing "Failed to register action for mode" errors in your logs, the UUID will typically be mentioned there.

1. **List all custom modes** to identify the problematic one:
   ```sh
   python3 - <<'PY'
   import json, sqlite3, pathlib

   path = pathlib.Path.home() / "Library/Application Support/Cursor/User/globalStorage/state.vscdb"
   key = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser"

   conn = sqlite3.connect(path)
   cur = conn.cursor()
   value, = cur.execute("SELECT value FROM ItemTable WHERE key=?", (key,)).fetchone()
   data = json.loads(value)

   modes = data.get("composerState", {}).get("modes4", [])
   print(f"Found {len(modes)} custom mode(s):")
   for i, mode in enumerate(modes, 1):
       print(f"\n{i}. Name: {mode.get('name', 'N/A')}")
       print(f"   ID: {mode.get('id', 'N/A')}")
       print(f"   Type: {mode.get('type', 'N/A')}")
   
   conn.close()
   PY
   ```
   Review the output and note the UUID of the mode you want to remove.

2. **Remove the problematic mode** by replacing `YOUR_MODE_UUID_HERE` with the UUID from step 1:
   ```sh
   python3 - <<'PY'
   import json, sqlite3, pathlib

   path = pathlib.Path.home() / "Library/Application Support/Cursor/User/globalStorage/state.vscdb"
   key = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser"
   mode_id = "YOUR_MODE_UUID_HERE"  # Replace with the actual UUID

   conn = sqlite3.connect(path)
   cur = conn.cursor()
   value, = cur.execute("SELECT value FROM ItemTable WHERE key=?", (key,)).fetchone()
   data = json.loads(value)

   composer = data.get("composerState", {})
   modes = composer.get("modes4", [])
   original_count = len(modes)
   composer["modes4"] = [mode for mode in modes if mode.get("id") != mode_id]
   removed_count = original_count - len(composer["modes4"])

   if removed_count > 0:
       cur.execute("UPDATE ItemTable SET value=? WHERE key=?", (json.dumps(data, separators=(',', ':')), key))
       conn.commit()
       print(f"✓ Removed {removed_count} mode(s) with ID: {mode_id}")
   else:
       print(f"✗ No mode found with ID: {mode_id}")
   
   conn.close()
   PY
   ```

3. **Verify removal** (replace `YOUR_MODE_UUID_HERE` with the UUID you removed):
   ```sh
   sqlite3 ~/Library/Application\ Support/Cursor/User/globalStorage/state.vscdb \
     "SELECT count(*) FROM ItemTable WHERE value LIKE '%YOUR_MODE_UUID_HERE%';"
   ```
   Expect `0`. If it still returns `1`, rerun step 2 after quitting Cursor again.

4. *(Optional)* **Vacuum the database** to reclaim space:
   ```sh
   sqlite3 ~/Library/Application\ Support/Cursor/User/globalStorage/state.vscdb "VACUUM;"
   ```

## If the Entry Returns
- The custom mode can reappear if Cursor rewrites its state while running. Repeat the steps above, but make sure Cursor stays closed until you finish verification.
- If problems persist, restore the backup (`cp state.vscdb.bak.YYYYMMDDHHMMSS state.vscdb`) and contact Cursor support with the logs referencing the problematic mode UUID.

## Quick Checklist
- [ ] Cursor fully closed before edits
- [ ] Database backed up
- [ ] Listed all custom modes (step 1)
- [ ] Identified problematic mode UUID
- [ ] Mode removed via Python script (step 2)
- [ ] Post-check shows `0` results for the UUID (step 3)
- [ ] Optional vacuum completed (step 4)

3 Likes

This worked for me! Nice job.

I also found disabling the Plan Mode in Beta temporarily fixed the issue for me, however, when I updated to 2.0 there was no more option to disable Plan Mode in Beta so I was stuck, until I tried this codex prompt.

Amazing! Thanks!

Here’s a corresponding Python script that does what your code mentions.

import datetime
import json
import pathlib
import shutil
import sqlite3


def main():
    """
    An interactive script to manage and remove conflicting custom modes from Cursor's state database.
    """
    # --- Configuration ---
    try:
        db_path = pathlib.Path.home() / "Library/Application Support/Cursor/User/globalStorage/state.vscdb"
        db_key = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser"
    except Exception as e:
        print(f"Error setting up initial paths: {e}")
        return

    # --- 1. Pre-flight checks and Backup ---
    if not db_path.exists():
        print(f"Error: Database not found at {db_path}")
        print("Please ensure you are running this on a machine with Cursor installed.")
        return

    print("--- Cursor Custom Mode Fixer ---")
    print("\nIMPORTANT: Please ensure Cursor.app is completely closed before proceeding.")
    try:
        input("Press Enter to continue or Ctrl+C to exit...")
    except KeyboardInterrupt:
        print("\nOperation cancelled by user.")
        return

    try:
        backup_path = db_path.parent / f"state.vscdb.bak.{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}"
        print(f"\nBacking up current database to: {backup_path}")
        shutil.copy(db_path, backup_path)
        print("✓ Backup successful.")
    except (OSError, shutil.Error) as e:
        print(f"✗ Error creating backup: {e}")
        print("Aborting to prevent data loss.")
        return

    # --- 2. Connect and Read Data ---
    conn = None
    try:
        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        result = cur.execute("SELECT value FROM ItemTable WHERE key=?", (db_key,)).fetchone()

        if not result:
            print(f"✗ Error: Could not find the required key ('{db_key}') in the database.")
            conn.close()
            return

        (value,) = result
        data = json.loads(value)
        composer_state = data.get("composerState", {})
        modes = composer_state.get("modes4", [])

    except (sqlite3.Error, json.JSONDecodeError) as e:
        print(f"✗ An error occurred while reading the database: {e}")
        if conn:
            conn.close()
        return

    # --- 3. List Modes ---
    if not modes:
        print("\nNo custom modes found. Nothing to do.")
        conn.close()
        return

    print("\n" + "-" * 30)
    print(f"Found {len(modes)} custom mode(s):")
    for i, mode in enumerate(modes, 1):
        print(f"\n{i}. Name: {mode.get('name', 'N/A')}")
        print(f"   ID:   {mode.get('id', 'N/A')}")
        print(f"   Type: {mode.get('type', 'N/A')}")
    print("-" * 30)

    # --- 4. Get User Input for Deletion ---
    try:
        mode_id_to_remove = input("\nEnter the full ID of the mode to remove (or press Enter to skip): ").strip()
    except KeyboardInterrupt:
        print("\nOperation cancelled by user.")
        conn.close()
        return

    if not mode_id_to_remove:
        print("\nSkipping removal. No changes made.")
        conn.close()
        return

    # --- 5. Remove the Mode ---
    original_count = len(modes)
    updated_modes = [mode for mode in modes if mode.get("id") != mode_id_to_remove]
    removed_count = original_count - len(updated_modes)

    if removed_count > 0:
        composer_state["modes4"] = updated_modes
        # No need for data["composerState"] = composer_state, it's a mutable dict

        try:
            # --- 6. Update Database ---
            updated_value = json.dumps(data, separators=(",", ":"))
            cur.execute("UPDATE ItemTable SET value=? WHERE key=?", (updated_value, db_key))
            conn.commit()
            print(f"\n✓ Successfully removed {removed_count} mode(s) with ID: {mode_id_to_remove}")

            # --- 7. Vacuum Database ---
            print("Vacuuming database to reclaim space...")
            conn.execute("VACUUM;")
            conn.commit()
            print("✓ Database vacuumed.")

        except sqlite3.Error as e:
            print(f"✗ An error occurred while updating the database: {e}")
            print("Rolling back changes.")
            conn.rollback()
    else:
        print(f"\n✗ No mode found with ID: {mode_id_to_remove}")

    # --- 8. Close Connection ---
    conn.close()
    print("\nProcess complete.")


if __name__ == "__main__":
    main()
2 Likes

You can disable Plan Mode in Cursor Settings > Beta, then restart. The mode list should load normally - you can then remove the conflicting mode and re-enable Cursor’s native Plan Mode in Beta (if needed)

This works for version before v2.0. In addition, for every workspace I used, I had to disable the Beta → Plan mode. It wasn’t a proper fix.

Especially for the version v2.0. After the update, there’s no way to disable, as the Plan mode is out of Beta. You will need to execute the weird clean-up script. It is anyways the correct fix as it was just a name-clash between the official Plan mode and some other odd Plan mode that was somehow not cleaned-up properly before.

Glad there’s a fix on this thread but VERY disappointed that Cursor 2.0 was released with this bug still in place.

1 Like

You, ma man, is da real MVP. Thank you!@

Thanks for the script @shern2 ! It solved the issue.

(Consider renaming the custom Plan mode instead of deleting it to preserve the custom instructions.)

This topic was automatically closed 22 days after the last reply. New replies are no longer allowed.