deepseek-ai/deepseek-harness · error

${duplicateMessage}

Error message

${duplicateMessage}

What it means

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.

Source

Thrown at packages/client/runtime/src/client/conversation/definition-registry.ts:41

    this.listeners.add(listener)
    return () => { this.listeners.delete(listener) }
  }

  /**
   * Register one uniquely keyed Definition for the caller's lifetime.
   * @param key - registry-local unique key.
   * @param definition - contributed Definition.
   * @param duplicateMessage - error raised when the key is already owned.
   * @param effectName - Cordis effect diagnostic label.
   * @returns idempotent disposer.
   */
  protected registerDefinition(
    key: string,
    definition: Definition,
    duplicateMessage: string,
    effectName: string,
  ): () => void {
    if (this.definitions.has(key)) throw new Error(duplicateMessage)
    const owner = this.ctx
    const dispose = owner.effect(() => {
      this.definitions.set(key, definition)
      this.refresh()
      return () => {
        if (this.definitions.get(key) !== definition) return
        this.definitions.delete(key)
        this.refresh()
      }
    }, effectName)
    return () => { void dispose() }
  }

  /** Refresh cached entries and synchronously invalidate subscribers. */
  protected refresh(): void {
    this.cached = [...this.definitions.values()]
    for (const listener of this.listeners) listener()
  }

View on GitHub (pinned to b150a551b8)

Solutions

  1. Make the kind unique per business Definition — prefix by domain or owning package (e.g. 'tool.call.toolview' vs 'tool.call.chart').
  2. Check for double application: the same package mounted twice, or a fixture plus the real plugin both live — remove one or gate the fixture.
  3. Ensure disposers run before re-registration under HMR or repeated test setup: dispose the previous fiber/registration first.

Example fix

// before — two packages both claim 'tool.call'
events.register({ kind: 'tool.call', ... })
events.register({ kind: 'tool.call', ... }) // throws: already registered

// after — kinds are unique per owning package
events.register({ kind: 'tool.call.toolview', ... })  // ui-conversation
events.register({ kind: 'tool.call.chart', ... })     // ui-charts
Defensive patterns

Strategy: validation

Validate before calling

const kindTaken = (events: ConversationEventRegistry, kind: string) =>
  events.entries().some(d => d.kind === kind)
if (kindTaken(events, myKind)) throw new Error(`${myKind} already owned — pick a unique kind`)
events.register(definition)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24). Data as JSON: /api/errors/826d0961f091dd2a. Report an issue: GitHub.