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
- Remove WithCreatedNotify from the put call
- Scope watch options (WithCreatedNotify, WithFilterPut, WithFilterDelete, WithProgressNotify, WithPrevKV as appropriate) to watcher code only
- Give puts their own narrow option builder that only accepts WithLease/WithIgnoreValue/WithIgnoreLease
- 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
- Do not feed watch option lists into OpPut
- Reject config-driven 'apply to everything' option maps
- Test all constructed ops in unit tests
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
- unexpected sort in put
- unexpected serializable in put
- unexpected countOnly in put
- unexpected mod revision filter in put
- unexpected create revision filter in put
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/760ae92c70c98d1f.
Report an issue: GitHub.