{"record":{"id":"029af87324a485d8","repo":"dotnet/runtime","slug":"startup-diagnostic-client-already-registered","errorCode":null,"errorMessage":"startup diagnostic client already registered","messagePattern":"startup diagnostic client already registered","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts","lineNumber":154,"sourceCode":"            downloadBlob(this.messagesToSend);\n        }\n        this.messagesToSend = [];\n        return 0;\n    }\n}\n\nexport function initializeJsClient() {\n    nextJsClient = dotnetLoaderExports.createPromiseCompletionSource<IDiagnosticClient>();\n    startupJsClient = undefined;\n}\n\nexport function setupJsClient(client: IDiagnosticClient, startup?: boolean) {\n    if (!startup && !dotnetLoaderExports.isRuntimeRunning()) {\n        throw new Error(\"Runtime is not running\");\n    }\n    if (startup) {\n        if (startupJsClient) {\n            throw new Error(\"startup diagnostic client already registered\");\n        }\n        startupJsClient = client;\n    } else {\n        if (nextJsClient.isDone) {\n            throw new Error(\"multiple clients in parallel are not allowed\");\n        }\n        nextJsClient.resolve(client);\n    }\n}\n\nexport function createDiagConnectionJs(socketHandle: number, scenarioName: string): DiagnosticSession {\n    if (!fromScenarioNameOnce) {\n        fromScenarioNameOnce = true;\n        if (scenarioName.startsWith(\"js://gcdump\")) {\n            collectGcDump({}, true);\n        }\n        if (scenarioName.startsWith(\"js://counters\")) {\n            collectMetrics({}, true);","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/dotnet/runtime/blob/60108ba66eb7d1d12f595480091b4ad80a24b172/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts#L136-L172","documentation":"setupJsClient() with startup=true throws if startupJsClient is already set. Only one startup diagnostic client is allowed because the diagnostic server reserves a single slot for the client that handles the very first advert (the runtime's banner). Registering twice would orphan the first client.","triggerScenarios":"Calling collectMetrics({}, true) and then collectCpuSamples({}, true) — both try to register as the startup client. Calling createDiagConnectionJs with multiple scenario names that each register. A library and the app both calling setupJsClient(client, true).","commonSituations":"Two diagnostic modules (counters + cpu-profiler) both initialized at startup. Plugin architecture where each plugin registers its own startup client. Re-invoking collect* with startup=true after a hot reload.","solutions":["Register exactly one startup client; use the scenario-name switch in createDiagConnectionJs to multiplex.","If you need multiple collection types, register one startup client and dispatch inside its callbacks.","For ad-hoc collection after boot, call collectMetrics() without startup=true.","Guard: only call setupJsClient(client, true) once per page load."],"exampleFix":"// before: two startup clients\ncollectMetrics({}, true);\ncollectCpuSamples({}, true); // throws\n\n// after: only one startup, switch later\ncollectMetrics({}, true); // startup slot taken\n// later, after runtime is up:\ncollectCpuSamples(); // runtime-time collection","handlingStrategy":"validation","validationCode":"let startupClientRegistered = false;\nfunction registerStartupSafely(client: IDiagnosticClient) {\n  if (startupClientRegistered) throw new Error('Startup client already set');\n  setupJsClient(client, true);\n  startupClientRegistered = true;\n}","typeGuard":"function isStartupSlotFree(): boolean {\n  // Reflect on the diagnostic-server-js module state via the loader's exports\n  return !(globalThis as any).__diagStartupClientRegistered;\n}","tryCatchPattern":"try { collectMetrics({}, true); } catch (e) {\n  if (/already registered/.test((e as Error).message)) {\n    // a startup client exists; use runtime-time collection instead\n    await dotnet.create();\n    collectMetrics();\n  } else throw e;\n}","preventionTips":["Designate exactly one startup client in your app architecture.","Track registration in a module-level boolean and check before setupJsClient(client, true).","Document the startup-client contract for plugin authors.","Use createDiagConnectionJs scenario names to multiplex collection types."],"tags":["diagnostics","lifecycle","singleton"],"backgroundTag":null,"analyzedSha":"60108ba66eb7d1d12f595480091b4ad80a24b172","analyzedAt":"2026-08-10T18:54:11.478Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}