{"record":{"id":"2399b38f4ed05a39","repo":"etcd-io/etcd","slug":"cannot-call-then-twice","errorCode":null,"errorMessage":"cannot call Then twice!","messagePattern":"cannot call Then twice!","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/txn.go","lineNumber":104,"sourceCode":"\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!\")\n\t}\n\tif txn.celse {\n\t\tpanic(\"cannot call Then after Else!\")\n\t}\n\n\ttxn.cthen = true\n\n\tfor _, op := range ops {\n\t\ttxn.isWrite = txn.isWrite || op.isWrite()\n\t\ttxn.sus = append(txn.sus, op.toRequestOp())\n\t}\n\n\treturn txn\n}\n\nfunc (txn *txn) Else(ops ...Op) Txn {\n\ttxn.mu.Lock()\n\tdefer txn.mu.Unlock()","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/txn.go#L86-L122","documentation":"The txn builder panics with 'cannot call Then twice!' when Then() is called more than once on the same Txn. A transaction has exactly one then-branch; the cthen flag makes a second Then a grammar violation that fails fast with a panic before any request is built. Combine all then-ops in a single call — they run atomically.","triggerScenarios":"Calling t.Then(op1).Then(op2) on one Txn, or appending then-ops incrementally from multiple helper functions. Loops that call Then per operation instead of passing an ops slice once.","commonSituations":"Accumulating write operations across code layers; translating SQL-style logic where multiple update statements were sequential; refactors that split one Then call into several without collecting ops first.","solutions":["Pass all then-ops in one call: Then(op1, op2, ...) — they execute atomically on success","Collect []clientv3.Op in a slice and call Then(ops...) once","Change helpers to return Op values instead of mutating the Txn","If you truly need two sequential transactions, create two separate Txn instances"],"exampleFix":"// before\nt := cli.Txn(ctx).If(c).Then(clientv3.OpPut(\"a\", \"1\"))\nt = t.Then(clientv3.OpPut(\"b\", \"2\")) // panics\n\n// after\nt := cli.Txn(ctx).If(c).Then(\n\tclientv3.OpPut(\"a\", \"1\"),\n\tclientv3.OpPut(\"b\", \"2\"),\n)","handlingStrategy":"validation","validationCode":"// Collect then-ops, single Then call:\nvar thenOps []clientv3.Op\nfor _, k := range keys {\n\tthenOps = append(thenOps, clientv3.OpPut(k, \"v\"))\n}\nt := cli.Txn(ctx).If(c).Then(thenOps...)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["One Then per Txn; all ops in one slice","Helpers return Op, not chained Then calls","Separate Txn instances for sequential transactions"],"tags":["etcd","clientv3","panic","api-misuse","txn","fluent-builder"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}