{"record":{"id":"826d0961f091dd2a","repo":"deepseek-ai/deepseek-harness","slug":"duplicatemessage","errorCode":null,"errorMessage":"${duplicateMessage}","messagePattern":"\\$\\{duplicateMessage\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/client/runtime/src/client/conversation/definition-registry.ts","lineNumber":41,"sourceCode":"    this.listeners.add(listener)\n    return () => { this.listeners.delete(listener) }\n  }\n\n  /**\n   * Register one uniquely keyed Definition for the caller's lifetime.\n   * @param key - registry-local unique key.\n   * @param definition - contributed Definition.\n   * @param duplicateMessage - error raised when the key is already owned.\n   * @param effectName - Cordis effect diagnostic label.\n   * @returns idempotent disposer.\n   */\n  protected registerDefinition(\n    key: string,\n    definition: Definition,\n    duplicateMessage: string,\n    effectName: string,\n  ): () => void {\n    if (this.definitions.has(key)) throw new Error(duplicateMessage)\n    const owner = this.ctx\n    const dispose = owner.effect(() => {\n      this.definitions.set(key, definition)\n      this.refresh()\n      return () => {\n        if (this.definitions.get(key) !== definition) return\n        this.definitions.delete(key)\n        this.refresh()\n      }\n    }, effectName)\n    return () => { void dispose() }\n  }\n\n  /** Refresh cached entries and synchronously invalidate subscribers. */\n  protected refresh(): void {\n    this.cached = [...this.definitions.values()]\n    for (const listener of this.listeners) listener()\n  }","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/client/runtime/src/client/conversation/definition-registry.ts#L23-L59","documentation":"registerDefinition() is the shared keyed-registration path of the conversation Definition registries; ConversationEventRegistry.register keys each Definition by its kind, and the concrete message reads: conversation Definition \"<kind>\" is already registered. It throws when the key is already owned: the duplicate check runs immediately, before the effect installs the definition, so a second live registration of the same kind is always an error, never an overwrite. Registration is fiber-scoped and removed by the disposer only when the same definition still owns the key.","triggerScenarios":"Two plugins (or the same plugin applied twice without disposal) call conversationEvents.register() with Definitions whose kind strings are equal; also HMR or repeated test setups where the previous fiber's disposer has not run before re-apply.","commonSituations":"Copying an existing UI plugin package and forgetting to rename its Definition kind; a test fixture registering a stub with the production kind while the real plugin is also composed; double ctx.plugin() of the same package in a hand-built harness.","solutions":["Make the kind unique per business Definition — prefix by domain or owning package (e.g. 'tool.call.toolview' vs 'tool.call.chart').","Check for double application: the same package mounted twice, or a fixture plus the real plugin both live — remove one or gate the fixture.","Ensure disposers run before re-registration under HMR or repeated test setup: dispose the previous fiber/registration first."],"exampleFix":"// before — two packages both claim 'tool.call'\nevents.register({ kind: 'tool.call', ... })\nevents.register({ kind: 'tool.call', ... }) // throws: already registered\n\n// after — kinds are unique per owning package\nevents.register({ kind: 'tool.call.toolview', ... })  // ui-conversation\nevents.register({ kind: 'tool.call.chart', ... })     // ui-charts","handlingStrategy":"validation","validationCode":"const kindTaken = (events: ConversationEventRegistry, kind: string) =>\n  events.entries().some(d => d.kind === kind)\nif (kindTaken(events, myKind)) throw new Error(`${myKind} already owned — pick a unique kind`)\nevents.register(definition)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive the kind from the owning package/domain so collisions are structural, not accidental.","Keep test fixtures on fixture-only kinds, never the production kind.","Assert registration disposal in HMR-safety tests so re-apply never double-registers."],"tags":["registry","duplicate-key","conversation","plugin-package"],"backgroundTag":"duplicate-registration-key","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}