etcd-io/etcd · error

unexpected filter in put

Error message

unexpected filter in put

What it means

OpPut panics with 'unexpected filter in put' when WithFilterDelete or WithFilterPut is applied to a put. These filters tell a range/watch to drop deletion or put events from results and only make sense on queries; OpPut rejects them at construction time. The shared message 'unexpected filter in put' covers both filter flags.

Source

Thrown at client/v3/op.go:322

	switch {
	case ret.end != nil:
		panic("unexpected range in put")
	case ret.limit != 0:
		panic("unexpected limit in put")
	case ret.rev != 0:
		panic("unexpected revision in put")
	case ret.sort != nil:
		panic("unexpected sort in put")
	case ret.serializable:
		panic("unexpected serializable in put")
	case ret.countOnly:
		panic("unexpected countOnly in put")
	case ret.minModRev != 0, ret.maxModRev != 0:
		panic("unexpected mod revision filter in put")
	case ret.minCreateRev != 0, ret.maxCreateRev != 0:
		panic("unexpected create revision filter in put")
	case ret.filterDelete, ret.filterPut:
		panic("unexpected filter in put")
	case ret.createdNotify:
		panic("unexpected createdNotify in put")
	}
	return ret
}

// OpTxn returns "txn" operation based on given transaction conditions.
func OpTxn(cmps []Cmp, thenOps []Op, elseOps []Op) Op {
	clonedCmps := make([]Cmp, len(cmps))
	for i := range cmps {
		clonedCmps[i] = cmps[i].Clone()
	}
	return Op{t: tTxn, cmps: clonedCmps, thenOps: thenOps, elseOps: elseOps}
}

func OpWatch(key string, opts ...OpOption) Op {
	ret := Op{t: tRange, key: []byte(key)}
	ret.applyOpts(opts)

View on GitHub (pinned to f744d457f4)

Solutions

  1. Remove WithFilterDelete/WithFilterPut from the put call
  2. Build the write op with zero options (a plain OpPut(key, val)) unless you specifically need WithLease or WithPrevKV, which are the put-valid ones
  3. Isolate watch/query option assembly from write option assembly in your codebase
  4. Check the panic family list in client/v3/op.go OpPut to see the full set of invalid options

Example fix

// before
op := clientv3.OpPut("k", "v", clientv3.WithFilterDelete(), clientv3.WithFilterPut())

// after
op := clientv3.OpPut("k", "v")
Defensive patterns

Strategy: validation

Validate before calling

// Event filters are watch-only; keep them next to the Watch call.
wch := cli.Watch(ctx, "k", clientv3.WithFilterDelete(), clientv3.WithFilterPut())
op := clientv3.OpPut("k", "v") // plain put, no filters

Prevention

When it happens

Trigger: Calling clientv3.OpPut(key, val, clientv3.WithFilterDelete()) or WithFilterPut(). Typically the options were assembled for a watch or a Get over historical events and reused for a write, e.g. replaying an event log and persisting state with the same option slice.

Common situations: Watch-event processors that read with event filters and then write derived state; option slices shared between watch setup and put; migrating from KV.Get to explicit Op construction while keeping old options.

Related errors


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