{"record":{"id":"b66ee48e324822f2","repo":"dgraph-io/badger","slug":"nil-callback-provided-to-commitwith","errorCode":null,"errorMessage":"Nil callback provided to CommitWith","messagePattern":"Nil callback provided to CommitWith","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"txn.go","lineNumber":702,"sourceCode":"\tcase cb.user == nil:\n\t\tpanic(\"Must have caught a nil callback for txn.CommitWith\")\n\tcase cb.err != nil:\n\t\tcb.user(cb.err)\n\tcase cb.commit != nil:\n\t\terr := cb.commit()\n\t\tcb.user(err)\n\tdefault:\n\t\tcb.user(nil)\n\t}\n}\n\n// CommitWith acts like Commit, but takes a callback, which gets run via a\n// goroutine to avoid blocking this function. The callback is guaranteed to run,\n// so it is safe to increment sync.WaitGroup before calling CommitWith, and\n// decrementing it in the callback; to block until all callbacks are run.\nfunc (txn *Txn) CommitWith(cb func(error)) {\n\tif cb == nil {\n\t\tpanic(\"Nil callback provided to CommitWith\")\n\t}\n\n\tif len(txn.pendingWrites) == 0 {\n\t\t// Do not run these callbacks from here, because the CommitWith and the\n\t\t// callback might be acquiring the same locks. Instead run the callback\n\t\t// from another goroutine.\n\t\tgo runTxnCallback(&txnCb{user: cb, err: nil})\n\t\t// Discard the transaction so that the read is marked done.\n\t\ttxn.Discard()\n\t\treturn\n\t}\n\n\t// Precheck before discarding txn.\n\tif err := txn.commitPrecheck(); err != nil {\n\t\tcb(err)\n\t\treturn\n\t}\n","sourceCodeStart":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/dgraph-io/badger/blob/2a001d466f6b71a917319a1db41f99860e16e269/txn.go#L684-L720","documentation":"CommitWith requires a callback because the commit result is delivered asynchronously via a goroutine. If you pass nil, Badger panics immediately at the public API boundary so the problem is caught before any commit work starts. Use Commit instead if you don't need a callback.","triggerScenarios":"Calling txn.CommitWith(nil), or passing a variable holding a nil func(error) (including a typed-nil function value).","commonSituations":"Conditional callback assignment where the variable ends up nil; refactoring from Commit to CommitWith and forgetting to supply the callback; generic wrapper code that forwards a nil cb.","solutions":["Pass a real callback: txn.CommitWith(func(err error){ ... })","If no callback is needed, call txn.Commit() instead","Guard wrappers: if cb == nil { return txn.Commit() }; txn.CommitWith(cb)"],"exampleFix":"// before\ntxn.CommitWith(nil)\n// after\ntxn.CommitWith(func(err error) {\n    if err != nil { log.Printf(\"commit failed: %v\", err) }\n})","handlingStrategy":"validation","validationCode":"if cb == nil { return errors.New(\"callback required; use txn.Commit() if none needed\") }\ntxn.CommitWith(cb)","typeGuard":null,"tryCatchPattern":"defer func(){ if r := recover(); r != nil { if s, ok := r.(string); ok && strings.Contains(s, \"Nil callback\") { log.Error(s) } } }()","preventionTips":["Default to txn.Commit() when no async handling is needed","Never forward nil callbacks through generic wrappers","Initialize callback variables with a no-op func rather than leaving them nil"],"tags":["panic","nil-callback","public-api","commit"],"backgroundTag":"nil-callback-panic","analyzedSha":"2a001d466f6b71a917319a1db41f99860e16e269","analyzedAt":"2026-09-05T13:00:02.264Z","contentChangedAt":"2026-09-05T13:00:02.264Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}