musistudio/claude-code-router · warning

[deep-link] Failed to register ${appDeepLinkProtocol} protoc

Error message

[deep-link] Failed to register ${appDeepLinkProtocol} protocol: ${formatError(error)}

What it means

Electron's app.setAsDefaultProtocolClient failed while registering the app's custom deep-link protocol. The call throws on some platforms when registry access (Windows) or LaunchServices registration (macOS) fails, and the wrapper logs it instead of crashing.

Source

Thrown at packages/electron/src/main/deep-link.ts:181

        dialog.showErrorBox("Failed to open CCR plugin app", detail);
      } catch {
        // The console error above remains available when the dialog API is unavailable.
      }
      windowsManager.showMainWindow();
    } finally {
      this.openingPluginRequests.delete(requestKey);
    }
  }

  private registerProtocolClient(): void {
    try {
      if (process.defaultApp && process.argv.length >= 2) {
        app.setAsDefaultProtocolClient(appDeepLinkProtocol, process.execPath, [path.resolve(process.argv[1])]);
        return;
      }
      app.setAsDefaultProtocolClient(appDeepLinkProtocol);
    } catch (error) {
      console.warn(`[deep-link] Failed to register ${appDeepLinkProtocol} protocol: ${formatError(error)}`);
    }
  }
}

async function ensurePluginAppUrlAvailable(config: AppConfig, appUrl: string, startedGateway: boolean): Promise<void> {
  try {
    await waitForPluginAppUrl(
      appUrl,
      startedGateway ? pluginAppStartupProbeTimeoutMs : pluginAppExistingGatewayProbeTimeoutMs
    );
    return;
  } catch (error) {
    if (startedGateway) {
      throw error;
    }
    console.warn(`[deep-link] Plugin app URL was not reachable on the running gateway; restarting gateway once. ${formatError(error)}`);
  }

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Run the app from a proper build/installer where process.defaultApp is false
  2. Verify the protocol name is unique and not registered by another application
  3. On Windows, check HKCU\Software\Classes\<protocol> manually and delete stale entries
  4. Wrap registration in a retry after app 'ready' if currently called before it

Example fix

// before
app.setAsDefaultProtocolClient(appDeepLinkProtocol);
// after
if (app.isReady()) {
  app.setAsDefaultProtocolClient(appDeepLinkProtocol);
} else {
  app.whenReady().then(() => app.setAsDefaultProtocolClient(appDeepLinkProtocol));
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!app.isReady()) { /* defer registration */ }

Type guard

const isProtocolName = (v: string): v is string => /^[a-z][a-z0-9+.-]*$/.test(v);

Try / catch

try { app.setAsDefaultProtocolClient(proto); } catch (e) { console.warn(`register failed: ${formatError(e)}`); }

Prevention

When it happens

Trigger: Calling register() during app startup on Windows without admin rights, a sandboxed/dev environment where HKCU registry writes fail, or a protocol already claimed by another app. Also fires when process.defaultApp path handling passes a bad argv[1] in dev mode.

Common situations: Running unpackaged (electron .) where execPath/argv differ, corporate machines with registry policies, macOS during CI without a proper bundle id, or antivirus blocking registry writes.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/c391cb136e29837f. Report an issue: GitHub.