etcd-io/etcd · error

unexpected createdNotify in put

Error message

unexpected createdNotify in put

What it means

OpPut panics with 'unexpected createdNotify in put' when WithCreatedNotify is applied to a put. CreatedNotify configures a watch to emit a synthetic event when the watch is established, which is meaningless for writes; OpPut's validation switch panics immediately. Note WithCreatedNotify only affects ops converted to watch requests, never puts.

Source

Thrown at client/v3/op.go:324

		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)
	switch {
	case ret.leaseID != 0:

View on GitHub (pinned to f744d457f4)

Solutions

  1. Remove WithCreatedNotify from the put call
  2. Scope watch options (WithCreatedNotify, WithFilterPut, WithFilterDelete, WithProgressNotify, WithPrevKV as appropriate) to watcher code only
  3. Give puts their own narrow option builder that only accepts WithLease/WithIgnoreValue/WithIgnoreLease
  4. Add unit coverage of option builders so misuse panics surface in tests

Example fix

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

// after (watch keeps the option, put does not)
wch := cli.Watch(ctx, "k", clientv3.WithCreatedNotify())
op := clientv3.OpPut("k", "v")
Defensive patterns

Strategy: validation

Validate before calling

// Watch-notification options stay on the watch:
wch := cli.Watch(ctx, "k", clientv3.WithCreatedNotify())
op := clientv3.OpPut("k", "v")

Prevention

When it happens

Trigger: Calling clientv3.OpPut(key, val, clientv3.WithCreatedNotify()). Happens when watch-construction code (Watcher.Watch with WithCreateNotify) shares an option list with put construction, or when a generic 'do anything' helper forwards all options to every op type.

Common situations: Config-driven operation builders where a single options map feeds Get/Put/Watch alike; copy-paste from a clientv3.Watcher setup; refactors that unified option handling across operation types.

Related errors


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