{"record":{"id":"7a9066445fd4a5cb","repo":"go-gorm/gorm","slug":"conflicting-callback-s-with-before-s","errorCode":null,"errorMessage":"conflicting callback %s with before %s","messagePattern":"conflicting callback (.+?) with before (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"callbacks.go","lineNumber":295,"sourceCode":"\t\t// show warning message the callback name already exists\n\t\tif idx := getRIndex(names, c.name); idx > -1 && !c.replace && !c.remove && !cs[idx].remove {\n\t\t\tc.processor.db.Logger.Warn(context.Background(), \"duplicated callback `%s` from %s\\n\", c.name, utils.FileWithLineNum())\n\t\t}\n\t\tnames = append(names, c.name)\n\t}\n\n\tsortCallback = func(c *callback) error {\n\t\tif c.before != \"\" { // if defined before callback\n\t\t\tif c.before == \"*\" && len(sorted) > 0 {\n\t\t\t\tif curIdx := getRIndex(sorted, c.name); curIdx == -1 {\n\t\t\t\t\tsorted = append([]string{c.name}, sorted...)\n\t\t\t\t}\n\t\t\t} else if sortedIdx := getRIndex(sorted, c.before); sortedIdx != -1 {\n\t\t\t\tif curIdx := getRIndex(sorted, c.name); curIdx == -1 {\n\t\t\t\t\t// if before callback already sorted, append current callback just after it\n\t\t\t\t\tsorted = append(sorted[:sortedIdx], append([]string{c.name}, sorted[sortedIdx:]...)...)\n\t\t\t\t} else if curIdx > sortedIdx {\n\t\t\t\t\treturn fmt.Errorf(\"conflicting callback %s with before %s\", c.name, c.before)\n\t\t\t\t}\n\t\t\t} else if idx := getRIndex(names, c.before); idx != -1 {\n\t\t\t\t// if before callback exists\n\t\t\t\tcs[idx].after = c.name\n\t\t\t}\n\t\t}\n\n\t\tif c.after != \"\" { // if defined after callback\n\t\t\tif c.after == \"*\" && len(sorted) > 0 {\n\t\t\t\tif curIdx := getRIndex(sorted, c.name); curIdx == -1 {\n\t\t\t\t\tsorted = append(sorted, c.name)\n\t\t\t\t}\n\t\t\t} else if sortedIdx := getRIndex(sorted, c.after); sortedIdx != -1 {\n\t\t\t\tif curIdx := getRIndex(sorted, c.name); curIdx == -1 {\n\t\t\t\t\t// if after callback sorted, append current callback to last\n\t\t\t\t\tsorted = append(sorted, c.name)\n\t\t\t\t} else if curIdx < sortedIdx {\n\t\t\t\t\treturn fmt.Errorf(\"conflicting callback %s with after %s\", c.name, c.after)","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/go-gorm/gorm/blob/1d6ce99528060be18a42be09aca8d39efcb47f28/callbacks.go#L277-L313","documentation":"When a processor sorts its callbacks (sortCallback in callbacks.go), each callback's Before constraint is checked against the already-sorted list. If the callback itself is already in the sorted list at a position AFTER the callback it declared Before (curIdx > sortedIdx), the ordering constraints are contradictory and GORM returns fmt.Errorf(\"conflicting callback %s with before %s\", c.name, c.before).","triggerScenarios":"Registering callbacks via db.Callback().Create().Before(\"gorm:create\").Register(\"my_cb\", ...) where earlier registrations already placed my_cb after gorm:create (e.g. via another callback's After(\"my_cb\") constraint), producing a cycle/contradiction; typically happens when multiple plugins register callbacks with mutual constraints.","commonSituations":"Two plugins (audit hooks, soft-delete, caching) each registering Before/After constraints that interact badly; upgrading a plugin that changed its constraint targets; re-registering a callback after Remove with different constraints in tests.","solutions":["Print db.Callback().Create().Get(...) / the registered names+constraints and identify the mutually contradictory pair.","Change one side's constraint (swap Before for After, or target a different anchor callback).","Update the plugin(s) to versions with compatible constraint sets.","In tests, use a fresh gorm.Session/DB or remove callbacks before re-registering with new constraints."],"exampleFix":"// before: plugin A\nc.Create().After(\"my_cb\").Register(\"audit\", fn)\n// plugin B\nc.Create().Before(\"audit\").Register(\"my_cb\", fn2) // contradictory\n\n// after: single consistent direction\nc.Create().Before(\"gorm:create\").Register(\"my_cb\", fn2)\nc.Create().Before(\"gorm:create\").Register(\"audit\", fn)","handlingStrategy":"validation","validationCode":"// startup self-check: register all callbacks, then force a sort by starting a session\nfunc initCallbacks(db *gorm.DB) error {\n    // ... register plugins ...\n    if err := db.Callback().Create().Replace(\"gorm:create\", db.Callback().Create().Register /* or keep */); err != nil { return err }\n    return nil // sort errors surface on first registration; catch them here at boot\n}","typeGuard":null,"tryCatchPattern":"if err := c.Register(\"my_cb\", fn); err != nil {\n    if strings.Contains(err.Error(), \"conflicting callback\") {\n        // adjust Before/After anchor and retry registration once\n    }\n    return err\n}","preventionTips":["Anchor plugin callbacks to core callbacks (gorm:create, gorm:query), not to other plugins' names.","Register each callback exactly once at startup; avoid re-registration in tests.","Fail fast at boot: exercise one Create/Query per processor after plugin init."],"tags":["gorm","callbacks","hooks","plugin-conflict","startup"],"backgroundTag":null,"analyzedSha":"1d6ce99528060be18a42be09aca8d39efcb47f28","analyzedAt":"2026-08-15T13:01:23.433Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}