{"record":{"id":"0556eca176c16b5a","repo":"etcd-io/etcd","slug":"cannot-call-then-after-else","errorCode":null,"errorMessage":"cannot call Then after Else!","messagePattern":"cannot call Then after Else!","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/txn.go","lineNumber":107,"sourceCode":"\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()\n\n\tif txn.celse {\n\t\tpanic(\"cannot call Else twice!\")","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/txn.go#L89-L125","documentation":"The txn builder panics with 'cannot call Then after Else!' when Then() is called after Else() has already been declared on the same Txn. The grammar order is If -> Then -> Else; once the else-branch is set (celse flag), adding a then-branch is a construction bug and the builder panics immediately. Nothing has been committed when this fires.","triggerScenarios":"Sequences like cli.Txn(ctx).Then(a).Else(b).Then(c), or cli.Txn(ctx).Else(b).Then(a) — any Then after an Else. Typically caused by reordered builder chains in refactored code or helpers appending then-ops after the else-branch was already supplied.","commonSituations":"Wrappers that let callers register then/else callbacks in arbitrary order; merges where a Then block got appended after the Else block; copy-paste insertion at the wrong position in a chain.","solutions":["Reorder so Then(...) precedes Else(...) on the Txn","Assemble thenOps and elseOps slices locally, then chain once: If(cs...).Then(thenOps...).Else(elseOps...)","Do not mutate a Txn across helpers; build it in exactly one function","Cover composite transactions with unit tests to catch chain-order regressions"],"exampleFix":"// before\nt := cli.Txn(ctx).If(c).Else(clientv3.OpGet(\"k\"))\nt = t.Then(clientv3.OpPut(\"k\", \"v\")) // panics\n\n// after\nt := cli.Txn(ctx).If(c).\n\tThen(clientv3.OpPut(\"k\", \"v\")).\n\tElse(clientv3.OpGet(\"k\"))","handlingStrategy":"validation","validationCode":"// Chain in canonical order, assembled from slices:\nt := cli.Txn(ctx).If(cmps...).Then(thenOps...).Else(elseOps...)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Then must precede Else","Never append Then from a helper after Else","Build the full chain in one function"],"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"}