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

  1. Change the scope parameter to exactly "key" or "prefix" (case-sensitive, lowercase).
  2. If scope is omitted, the default is "key", so drop the parameter entirely for exact-key watches.
  3. Validate user/config-supplied scope against an allowlist before calling watch.
  4. 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

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


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/0d6e9c0c5dba2076. Report an issue: GitHub.