{"record":{"id":"27a1d8ab2211b743","repo":"etcd-io/etcd","slug":"cannot-call-if-after-else","errorCode":null,"errorMessage":"cannot call If after Else!","messagePattern":"cannot call If after Else!","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/txn.go","lineNumber":86,"sourceCode":"\tfas []*pb.RequestOp\n\n\tcallOpts []grpc.CallOption\n}\n\nfunc (txn *txn) If(cs ...Cmp) Txn {\n\ttxn.mu.Lock()\n\tdefer txn.mu.Unlock()\n\n\tif txn.cif {\n\t\tpanic(\"cannot call If twice!\")\n\t}\n\n\tif txn.cthen {\n\t\tpanic(\"cannot call If after Then!\")\n\t}\n\n\tif txn.celse {\n\t\tpanic(\"cannot call If after Else!\")\n\t}\n\n\ttxn.cif = true\n\n\tfor i := range cs {\n\t\tcmp := cs[i].Clone()\n\t\ttxn.cmps = append(txn.cmps, cmp.GetCompare())\n\t}\n\n\treturn txn\n}\n\nfunc (txn *txn) Then(ops ...Op) Txn {\n\ttxn.mu.Lock()\n\tdefer txn.mu.Unlock()\n\n\tif txn.cthen {\n\t\tpanic(\"cannot call Then twice!\")","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/txn.go#L68-L104","documentation":"The txn builder panics with 'cannot call If after Else!' when If() is invoked after Else() on the same Txn. The Txn grammar permits exactly one ordered If/Then/Else sequence; the celse flag marks Else as declared, and a subsequent If is rejected with a panic under the txn mutex. The transaction has not touched the network at this point.","triggerScenarios":"Sequences like cli.Txn(ctx).Then(a).Else(b).If(c) — calling If after Else (with or without Then in between). Usually caused by helper layers appending conditions late, or by code that builds the fallback branch first and decides conditions afterwards.","commonSituations":"Framework/ORM-style wrappers where user code sets branches and infrastructure adds conditions later; refactors that moved If below Else; copy-paste of an If block into the wrong position in a builder chain.","solutions":["Move the If call before Then/Else in the chain","Assemble conditions and both branches as slices first, then run the single chain If(...).Then(...).Else(...) once","Avoid passing live Txn objects between layers; pass data, build the Txn in one function","Unit-test composite transaction construction paths"],"exampleFix":"// before\nt := cli.Txn(ctx).Then(clientv3.OpPut(\"k\", \"v\")).Else(clientv3.OpGet(\"k\"))\nt = t.If(clientv3.Compare(clientv3.Version(\"k\"), \"=\", 0)) // panics\n\n// after\nt := cli.Txn(ctx).\n\tIf(clientv3.Compare(clientv3.Version(\"k\"), \"=\", 0)).\n\tThen(clientv3.OpPut(\"k\", \"v\")).\n\tElse(clientv3.OpGet(\"k\"))","handlingStrategy":"validation","validationCode":"// Decide conditions before building any branch:\nif cond {\n\tt = cli.Txn(ctx).If(cmp).Then(a).Else(b)\n} else {\n\tt = cli.Txn(ctx).Then(a).Else(b)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call If after Else","Compute conditions up front","Keep Txn construction in exactly one place"],"tags":["etcd","clientv3","panic","api-misuse","txn","ordering"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}