{"record":{"id":"369a8b26c711f669","repo":"weaviate/weaviate","slug":"active-export-id-mismatch-expected-q-got-q","errorCode":null,"errorMessage":"active export ID mismatch: expected %q, got %q","messagePattern":"active export ID mismatch: expected %q, got %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"usecases/export/participant.go","lineNumber":325,"sourceCode":"\t\t\t\t// On error, clean up snapshots we took ownership of.\n\t\t\t\t// clearAndRelease may also try to clean up via the pending\n\t\t\t\t// field, but we nil it below. os.RemoveAll on an\n\t\t\t\t// already-removed dir is a no-op.\n\t\t\t\tp.pending = nil\n\t\t\t\tp.clearAndRelease()\n\t\t\t}\n\t\t\tp.mu.Unlock()\n\t\t}()\n\n\t\ttimer := p.abortTimer\n\t\tif timer == nil {\n\t\t\terrRet = fmt.Errorf(\"timer is nil. No export prepared\")\n\t\t\treturn errRet\n\t\t}\n\t\ttimer.Stop()\n\n\t\tif p.activeExport != exportID {\n\t\t\terrRet = fmt.Errorf(\"active export ID mismatch: expected %q, got %q\", p.activeExport, exportID)\n\t\t\treturn errRet\n\t\t}\n\n\t\tif p.preparedReq == nil {\n\t\t\terrRet = fmt.Errorf(\"no export prepared\")\n\t\t\treturn errRet\n\t\t}\n\t\tif p.preparedReq.ID != exportID {\n\t\t\terrRet = fmt.Errorf(\"export ID mismatch: expected %q, got %q\", p.preparedReq.ID, exportID)\n\t\t\treturn errRet\n\t\t}\n\t\t// Pointer identity check: if an Abort cleared the slot and a new\n\t\t// Prepare set a different *ExportRequest (possibly with the same\n\t\t// export ID) while we were doing backend I/O outside the lock,\n\t\t// the pointer will differ even though the ID matches. This is a\n\t\t// defense-in-depth guard — the coordinator prevents ID reuse via\n\t\t// checkIfExportExists, so this race cannot happen in practice.\n\t\tif p.preparedReq != req {","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/weaviate/weaviate/blob/75aa4b6d11f8818305aafd4440b4e32794f7ca04/usecases/export/participant.go#L307-L343","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log both p.activeExport (in the error message) and the ID you passed; align the caller with the currently active ID.","Abort the stale flow and run a fresh Prepare/Commit cycle for the export you actually want.","Serialize coordinator operations per node so only one Prepare/Commit lifecycle is in flight at a time.","Verify IDs are not being reused or swapped between nodes in distributed exports."],"exampleFix":"// before\nparticipant.Commit(ctx, oldID) // aborted elsewhere -> active export mismatch\n// after\nif participant.IsRunning(oldID) {\n    if err := participant.Commit(ctx, oldID); err != nil { return err }\n} else {\n    // fresh lifecycle\n    if err := participant.Prepare(ctx, newReq); err != nil { return err }\n    return participant.Commit(ctx, newReq.ID)\n}","handlingStrategy":"validation","validationCode":"// confirm this participant still owns this export before committing\nif !participant.IsRunning(exportID) && lastPreparedID != exportID {\n    return fmt.Errorf(\"export %q no longer active on this node\", exportID)\n}","typeGuard":"func matchesActive(active, want string) bool { return active != \"\" && active == want }","tryCatchPattern":"if err := participant.Commit(ctx, id); err != nil {\n    if strings.Contains(err.Error(), \"active export ID mismatch\") {\n        // another lifecycle took over — re-prepare before retrying\n        return retryWithFreshPrepare(ctx)\n    }\n    return err\n}","preventionTips":["Allow only one export lifecycle per node at a time in your coordinator.","Check IsRunning/the latest prepared ID before issuing Commit after retries.","Log the active vs expected IDs on mismatch to spot coordinator ID-swapping bugs."],"tags":["export","state-machine","id-mismatch","race-condition"],"backgroundTag":"active-export-id-mismatch","analyzedSha":"75aa4b6d11f8818305aafd4440b4e32794f7ca04","analyzedAt":"2026-09-04T14:58:20.392Z","contentChangedAt":"2026-09-04T14:58:20.392Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}