t8y2/dbx · error
ETCD_WATCH_SCOPE_INVALID
ETCD_WATCH_SCOPE_INVALID
Error message
ETCD_WATCH_SCOPE_INVALID: scope must be key or prefix
What it means
This error is thrown by the etcd2 driver's watchStart when the caller supplies a 'scope' parameter that is neither 'key' nor 'prefix'. Watches in etcd are limited to either an exact key or a key prefix, so any other scope value is rejected before a client is even opened. It is a fail-fast input validation for the watch API.
Source
Thrown at agents/drivers/etcd2-go/watch.go:222
func (s *etcd2Session) removeWatch(id string) *watchState {
s.watchesMu.Lock()
defer s.watchesMu.Unlock()
state := s.watches[id]
delete(s.watches, id)
return state
}
func (s *etcd2Session) watchStart(params map[string]json.RawMessage) (any, error) {
if s.watchCount() >= maxWatches {
return nil, fmt.Errorf("ETCD_WATCH_LIMIT: at most %d watches are allowed per connection", maxWatches)
}
key, err := keyBytesParam(params)
if err != nil {
return nil, err
}
scope := stringOrDefault(params, "scope", "key")
if scope != "key" && scope != "prefix" {
return nil, errors.New("ETCD_WATCH_SCOPE_INVALID: scope must be key or prefix")
}
client, err := s.activeClient()
if err != nil {
return nil, err
}
// startRevision maps to the v2 waitIndex: default to the current index+1.
requestedRevision := longOrNull(params, "startRevision")
var waitIndex int64
if requestedRevision != nil && *requestedRevision > 0 {
waitIndex = *requestedRevision
} else {
current, err := client.currentEtcdIndex()
if err != nil {
return nil, err
}
waitIndex = current + 1
}View on GitHub (pinned to c0390bff16)
Solutions
- Change the scope parameter to exactly "key" or "prefix" (case-sensitive, lowercase).
- If scope is omitted, the default is "key", so drop the parameter entirely for exact-key watches.
- Validate user/config-supplied scope against an allowlist before calling watch.
- For watching a range that is not a prefix, issue multiple prefix watches or filter results client-side with a key watch plus filtering.
Example fix
// before
params := map[string]any{"key": "/cfg/app", "scope": "subtree"}
// after
params := map[string]any{"key": "/cfg/app", "scope": "prefix"} Defensive patterns
Strategy: validation
Validate before calling
func validScope(p map[string]any) error {
s, _ := p["scope"].(string)
if s != "" && s != "key" && s != "prefix" {
return fmt.Errorf("scope %q must be \"key\" or \"prefix\"", s)
}
return nil
} Type guard
func isWatchScope(s string) bool { return s == "key" || s == "prefix" } Prevention
- Keep a constants list of allowed scope values ("key", "prefix") and use them, never raw strings
- Validate watch params centrally before any watch call
- Remember scope defaults to "key" when omitted
When it happens
Trigger: Calling the watch op with params {"key": "...", "scope": "keys"} or any misspelled value like "Key", "prefixes", or "dir"; omitting validation of scope before passing user-supplied params through.
Common situations: Typo in the scope string, dynamically building watch parameters from config or query strings where the scope comes from an unvalidated field, or porting code from another etcd library that uses different scope vocabulary (e.g. 'range').
Understand the failure class
Background: "must be positive", "Invalid value": how libraries reject invalid parameter values (ValueError, ArgumentError, INVALID_PARAMETER_VALUE) — this error's family across 28 libraries.
Related errors
- ETCD_WATCH_SCOPE_INVALID
- lease, ttl, and preserveLease cannot be specified together
- ttl must be a positive integer
- ETCD_INVALID_%s
- ETCD_%s_REQUIRED
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/0d6e9c0c5dba2076.
Report an issue: GitHub.