etcd-io/etcd · error

cannot call Then twice!

Error message

cannot call Then twice!

What it means

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.

Source

Thrown at client/v3/txn.go:104

		panic("cannot call If after Else!")
	}

	txn.cif = true

	for i := range cs {
		cmp := cs[i].Clone()
		txn.cmps = append(txn.cmps, cmp.GetCompare())
	}

	return txn
}

func (txn *txn) Then(ops ...Op) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.cthen {
		panic("cannot call Then twice!")
	}
	if txn.celse {
		panic("cannot call Then after Else!")
	}

	txn.cthen = true

	for _, op := range ops {
		txn.isWrite = txn.isWrite || op.isWrite()
		txn.sus = append(txn.sus, op.toRequestOp())
	}

	return txn
}

func (txn *txn) Else(ops ...Op) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

View on GitHub (pinned to f744d457f4)

Solutions

  1. Pass all then-ops in one call: Then(op1, op2, ...) — they execute atomically on success
  2. Collect []clientv3.Op in a slice and call Then(ops...) once
  3. Change helpers to return Op values instead of mutating the Txn
  4. If you truly need two sequential transactions, create two separate Txn instances

Example fix

// before
t := cli.Txn(ctx).If(c).Then(clientv3.OpPut("a", "1"))
t = t.Then(clientv3.OpPut("b", "2")) // panics

// after
t := cli.Txn(ctx).If(c).Then(
	clientv3.OpPut("a", "1"),
	clientv3.OpPut("b", "2"),
)
Defensive patterns

Strategy: validation

Validate before calling

// Collect then-ops, single Then call:
var thenOps []clientv3.Op
for _, k := range keys {
	thenOps = append(thenOps, clientv3.OpPut(k, "v"))
}
t := cli.Txn(ctx).If(c).Then(thenOps...)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15). Data as JSON: /api/errors/2399b38f4ed05a39. Report an issue: GitHub.