{"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":"validation","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/290d5ab72cc1102813fbc0406fb186ceadabc340/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts#L136-L172","documentation":"Thrown by setupJsClient when startup=true but a startup diagnostic client (startupJsClient) is already registered. The diagnostic subsystem accepts at most one startup client because there is a single startup handshake with the runtime.","triggerScenarios":"Calling setupJsClient (or two collect* functions) with startup=true more than once, e.g. collectMetrics({}, true) followed by collectGcDump({}, true) at boot.","commonSituations":"Configuring DOTNET_DiagnosticPorts to a startup scenario AND explicitly calling a collect function with startup=true; an init helper that double-registers.","solutions":["Register exactly one startup diagnostic client.","Run additional collections at runtime (startup=false) after the startup capture completes."],"exampleFix":"// before: two startup clients collide\ncollectMetrics({}, true);\ncollectGcDump({}, true); // throws\n// after: one startup, the rest at runtime\nawait collectMetrics({}, true);\nawait collectGcDump(); // runtime path after startup completes","handlingStrategy":"validation","validationCode":"// Track startup registration externally so you never double-register\nlet startupRegistered = false;\nfunction registerStartupCollect(fn, opts) {\n  if (startupRegistered) throw new Error('A startup diagnostic client is already registered.');\n  startupRegistered = true;\n  return fn(opts, true);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Register exactly one startup diagnostic client.","Do not combine a DOTNET_DiagnosticPorts startup scenario with an explicit collect*(opts, true).","Run all other collections at runtime after the startup client finishes."],"tags":["diagnostics","startup","singleton"],"analyzedSha":"290d5ab72cc1102813fbc0406fb186ceadabc340","analyzedAt":"2026-08-06T19:57:01.276Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}