deepseek-ai/deepseek-harness · error

conversation fallback Definition is already registered

Error message

conversation fallback Definition is already registered

What it means

The fallback slot is single-occupancy by design: exactly one Definition handles events that no keyed Definition matched. registerFallback() throws when a fallback is already installed — a second registration, even with a different kind, is refused rather than overriding. The disposer frees the slot, and only if the current fallback is still the same object.

Source

Thrown at packages/client/runtime/src/client/conversation/event-registry.ts:38

    assertDefinitionTarget(definition)
    return this.registerDefinition(
      definition.kind,
      definition,
      `conversation Definition "${definition.kind}" is already registered`,
      `conversationEvents.register(${JSON.stringify(definition.kind)})`,
    )
  }

  /**
   * Register the sole fallback used only when no ordinary Definition matches.
   * @param definition - fallback Definition.
   * @returns idempotent disposer.
   */
  registerFallback(definition: ConversationNodeDefinition): () => void {
    assertDefinitionTarget(definition)
    const target = definition.target
    if (target === undefined) throw new Error('conversation fallback Definition must declare a target')
    if (this.fallback !== undefined) throw new Error('conversation fallback Definition is already registered')
    const owner = this.ctx
    const dispose = owner.effect(() => {
      this.fallback = definition
      this.refresh()
      return () => {
        if (this.fallback !== definition) return
        this.fallback = undefined
        this.refresh()
      }
    }, `conversationEvents.registerFallback(${JSON.stringify(definition.kind)})`)
    return () => { void dispose() }
  }

  /**
   * Return the current unmatched-event fallback.
   * @returns installed fallback, when present.
   */
  fallbackEntry(): ConversationNodeDefinition | undefined {

View on GitHub (pinned to b150a551b8)

Solutions

  1. Keep exactly one fallback owner (the core conversation package); other features register keyed Definitions with unique kinds instead.
  2. If replacing the fallback is genuinely required, dispose the existing registration (or disable the owning plugin) first, then register — usually via restart.
  3. Remove fallback registration from test fixtures, or ensure the fallback-owning plugin is not composed in that test.

Example fix

// before — two packages both register a fallback
events.registerFallback(coreFallback)
events.registerFallback(customFallback) // throws: already registered

// after — one fallback owner; others contribute keyed Definitions
events.registerFallback(coreFallback)
events.register({ kind: 'my.card', ... })
Defensive patterns

Strategy: validation

Validate before calling

if (events.fallbackEntry() !== undefined) {
  // the single fallback slot is owned; contribute a keyed Definition instead
  events.register({ kind: 'my.kind', /* ... */ })
} else {
  events.registerFallback(myFallback)
}

Prevention

When it happens

Trigger: A second live call to conversationEvents.registerFallback() while a previous fallback registration is still installed — two packages both claiming the fallback role, or a re-mount whose earlier disposer has not run.

Common situations: A new plugin package tries to replace the built-in generic fallback by registering its own; test harnesses install a fallback fixture while the core conversation package (which owns the fallback) is also composed; HMR re-apply racing disposal.

Related errors


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