weaviate/weaviate · error

active export ID mismatch: expected %q, got %q

Error message

active export ID mismatch: expected %q, got %q

What it means

Commit's critical section checks p.activeExport against the requested exportID and fails when they differ. activeExport records which export currently owns the participant slot; a mismatch means another export ID holds the slot (or it is empty) by the time Commit takes the lock after the snapshot wait and backend initialization.

Source

Thrown at usecases/export/participant.go:325

				// On error, clean up snapshots we took ownership of.
				// clearAndRelease may also try to clean up via the pending
				// field, but we nil it below. os.RemoveAll on an
				// already-removed dir is a no-op.
				p.pending = nil
				p.clearAndRelease()
			}
			p.mu.Unlock()
		}()

		timer := p.abortTimer
		if timer == nil {
			errRet = fmt.Errorf("timer is nil. No export prepared")
			return errRet
		}
		timer.Stop()

		if p.activeExport != exportID {
			errRet = fmt.Errorf("active export ID mismatch: expected %q, got %q", p.activeExport, exportID)
			return errRet
		}

		if p.preparedReq == nil {
			errRet = fmt.Errorf("no export prepared")
			return errRet
		}
		if p.preparedReq.ID != exportID {
			errRet = fmt.Errorf("export ID mismatch: expected %q, got %q", p.preparedReq.ID, exportID)
			return errRet
		}
		// Pointer identity check: if an Abort cleared the slot and a new
		// Prepare set a different *ExportRequest (possibly with the same
		// export ID) while we were doing backend I/O outside the lock,
		// the pointer will differ even though the ID matches. This is a
		// defense-in-depth guard — the coordinator prevents ID reuse via
		// checkIfExportExists, so this race cannot happen in practice.
		if p.preparedReq != req {

View on GitHub (pinned to 75aa4b6d11)

Solutions

  1. Log both p.activeExport (in the error message) and the ID you passed; align the caller with the currently active ID.
  2. Abort the stale flow and run a fresh Prepare/Commit cycle for the export you actually want.
  3. Serialize coordinator operations per node so only one Prepare/Commit lifecycle is in flight at a time.
  4. Verify IDs are not being reused or swapped between nodes in distributed exports.

Example fix

// before
participant.Commit(ctx, oldID) // aborted elsewhere -> active export mismatch
// after
if participant.IsRunning(oldID) {
    if err := participant.Commit(ctx, oldID); err != nil { return err }
} else {
    // fresh lifecycle
    if err := participant.Prepare(ctx, newReq); err != nil { return err }
    return participant.Commit(ctx, newReq.ID)
}
Defensive patterns

Strategy: validation

Validate before calling

// confirm this participant still owns this export before committing
if !participant.IsRunning(exportID) && lastPreparedID != exportID {
    return fmt.Errorf("export %q no longer active on this node", exportID)
}

Type guard

func matchesActive(active, want string) bool { return active != "" && active == want }

Try / catch

if err := participant.Commit(ctx, id); err != nil {
    if strings.Contains(err.Error(), "active export ID mismatch") {
        // another lifecycle took over — re-prepare before retrying
        return retryWithFreshPrepare(ctx)
    }
    return err
}

Prevention

When it happens

Trigger: A second Prepare replaced activeExport while this Commit was doing backend I/O outside the lock; an Abort cleared activeExport (set to empty); coordinator committing against the wrong node with a stale ID.

Common situations: Coordinator retry logic issuing Prepare for a new export ID while an older Commit is still in flight; operator aborting the export while it was initializing; ID confusion in multi-tenant setups.

Related errors


AI-assisted analysis of weaviate/weaviate@75aa4b6d11 (2026-09-04). Data as JSON: /api/errors/369a8b26c711f669. Report an issue: GitHub.