{"record":{"id":"ede68325bb359730","repo":"etcd-io/etcd","slug":"cannot-call-if-after-then","errorCode":null,"errorMessage":"cannot call If after Then!","messagePattern":"cannot call If after Then!","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/txn.go","lineNumber":82,"sourceCode":"\n\tcmps []*pb.Compare\n\n\tsus []*pb.RequestOp\n\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()","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/txn.go#L64-L100","documentation":"The txn builder panics with 'cannot call If after Then!' when If() is called on a Txn that has already had Then() called. etcd's Txn grammar is strictly ordered If -> Then -> Else; the cthen flag records that Then was declared, and any later If is a programming error that fails fast with a panic. No RPC has been sent when this fires.","triggerScenarios":"Sequences like cli.Txn(ctx).Then(op).If(cmp), typically caused by calling If after a Then in reordered code, or by a helper that unconditionally adds conditions but is invoked after the caller already built the Then branch.","commonSituations":"Refactoring that moves condition construction after branch construction; wrapper APIs where the caller sets Then/Else first and a framework hook injects If later; merge/refactor mistakes where lines get shuffled.","solutions":["Reorder the calls so all If(...) come before Then(...)/Else(...) on that Txn instance","Build conditions and branches as local slices, then assemble Txn in one place: If(cs...).Then(ops...).Else(ops...)","Make helper functions pure (return Cmp/Op slices) so ordering cannot be violated piecemeal","Add a unit test per composite transaction your code builds to catch ordering bugs in CI"],"exampleFix":"// before\nt := cli.Txn(ctx).Then(clientv3.OpPut(\"k\", \"v\"))\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\"))","handlingStrategy":"validation","validationCode":"// Assemble data first, then the chain in order:\nt := cli.Txn(ctx).\n\tIf(cmps...).\n\tThen(thenOps...).\n\tElse(elseOps...) // If always before Then/Else","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Enforce If -> Then -> Else ordering in one builder function","Do not pass Txn objects across layers for later mutation","Unit-test composite transactions"],"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"}