etcd-io/etcd · error

cannot call If after Else!

Error message

cannot call If after Else!

What it means

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.

Source

Thrown at client/v3/txn.go:86

	fas []*pb.RequestOp

	callOpts []grpc.CallOption
}

func (txn *txn) If(cs ...Cmp) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.cif {
		panic("cannot call If twice!")
	}

	if txn.cthen {
		panic("cannot call If after Then!")
	}

	if txn.celse {
		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!")

View on GitHub (pinned to f744d457f4)

Solutions

  1. Move the If call before Then/Else in the chain
  2. Assemble conditions and both branches as slices first, then run the single chain If(...).Then(...).Else(...) once
  3. Avoid passing live Txn objects between layers; pass data, build the Txn in one function
  4. Unit-test composite transaction construction paths

Example fix

// before
t := cli.Txn(ctx).Then(clientv3.OpPut("k", "v")).Else(clientv3.OpGet("k"))
t = t.If(clientv3.Compare(clientv3.Version("k"), "=", 0)) // panics

// after
t := cli.Txn(ctx).
	If(clientv3.Compare(clientv3.Version("k"), "=", 0)).
	Then(clientv3.OpPut("k", "v")).
	Else(clientv3.OpGet("k"))
Defensive patterns

Strategy: validation

Validate before calling

// Decide conditions before building any branch:
if cond {
	t = cli.Txn(ctx).If(cmp).Then(a).Else(b)
} else {
	t = cli.Txn(ctx).Then(a).Else(b)
}

Prevention

When it happens

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

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

Related errors


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