libnyanpasu/clash-nyanpasu · error

prepare() called more than once with different identifiers.

Error message

prepare() called more than once with different identifiers.

What it means

The deep-link plugin stores the app identifier once via OnceCell (ID.set). If prepare() is invoked a second time with a different identifier string, set() returns Err and the .expect panics. This guards the invariant that one process has exactly one deep-link identifier for the Windows foreground-window workaround.

Source

Thrown at backend/tauri-plugin-deep-link/src/windows.rs:202

                        let pid = buf.parse::<u32>().unwrap_or(ASFW_ANY);
                        unsafe {
                            let success = AllowSetForegroundWindow(pid) != 0;
                            if !success {
                                eprintln!("AllowSetForegroundWindow failed.");
                            }
                        }
                        std::process::exit(0);
                    }
                    Err(e) => {
                        eprintln!("Failed to connect to local socket: {e}");
                        std::thread::sleep(std::time::Duration::from_millis(1));
                    }
                };
            }
        });

    ID.set(identifier.to_string())
        .expect("prepare() called more than once with different identifiers.");
}

/// Send a dummy keypress event so AllowSetForegroundWindow can succeed
fn dummy_keypress() {
    let keyboard_input_down = KEYBDINPUT {
        wVk: 0, // This doesn't correspond to any actual keyboard key, but should still function for the workaround.
        dwExtraInfo: 0,
        wScan: 0,
        time: 0,
        dwFlags: 0,
    };

    let mut keyboard_input_up = keyboard_input_down;
    keyboard_input_up.dwFlags = 0x0002; // KEYUP flag

    let input_down_u = INPUT_0 {
        ki: keyboard_input_down,
    };

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Call prepare() only once per process with a constant identifier string
  2. Verify the plugin isn't registered twice in the Tauri builder chain
  3. Make the identifier a compile-time constant (e.g. from tauri.conf.json identifier) rather than a computed value
  4. In tests, run each identifier in a separate process or reset state between runs

Example fix

// before
prepare(config.identifier.clone() + "-v2");
// after
prepare(env!("CARGO_PKG_NAME").to_string()); // stable, same value every call
Defensive patterns

Strategy: validation

Validate before calling

static INITIALIZED: AtomicBool = AtomicBool::new(false);
assert!(!INITIALIZED.swap(true, Ordering::SeqCst), "prepare() already called");

Try / catch

if ID.set(identifier.to_string()).is_err() {
    eprintln!("prepare() called twice with different identifiers");
}

Prevention

When it happens

Trigger: Calling plugin prepare()/deep_link APIs twice during startup with differing identifier values — typically registering the deep-link plugin twice, or re-invoking prepare after config reload changed the identifier.

Common situations: Accidentally adding the deep-link plugin (or a plugin depending on it) twice to the Tauri builder; dynamic identifier derived from config that differs across initialization passes; test harnesses rebuilding the app builder per test with different identifiers in one process.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/9a30583a47524b77. Report an issue: GitHub.