{"record":{"id":"c316e69ca02b8053","repo":"t8y2/dbx","slug":"etcd-watch-scope-invalid","errorCode":"ETCD_WATCH_SCOPE_INVALID","errorMessage":"ETCD_WATCH_SCOPE_INVALID: scope must be key or prefix","messagePattern":"ETCD_WATCH_SCOPE_INVALID: scope must be key or prefix","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/etcd-go/watch.go","lineNumber":227,"sourceCode":"func (s *etcdSession) removeWatch(id string) *watchState {\n\ts.watchesMu.Lock()\n\tdefer s.watchesMu.Unlock()\n\tstate := s.watches[id]\n\tdelete(s.watches, id)\n\treturn state\n}\n\nfunc (s *etcdSession) watchStart(params map[string]json.RawMessage) (any, error) {\n\tif s.watchCount() >= maxWatches {\n\t\treturn nil, fmt.Errorf(\"ETCD_WATCH_LIMIT: at most %d watches are allowed per connection\", maxWatches)\n\t}\n\tkey, err := keyBytesParam(params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tscope := stringOrDefault(params, \"scope\", \"key\")\n\tif scope != \"key\" && scope != \"prefix\" {\n\t\treturn nil, errors.New(\"ETCD_WATCH_SCOPE_INVALID: scope must be key or prefix\")\n\t}\n\tclient, err := s.activeClient()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trequestedRevision := longOrNull(params, \"startRevision\")\n\tvar startedRevision int64\n\tif requestedRevision != nil && *requestedRevision > 0 {\n\t\tstartedRevision = *requestedRevision\n\t} else {\n\t\tctx, cancel := context.WithTimeout(context.Background(), rpcTimeoutSeconds*time.Second)\n\t\t// Read the revision from the same Key scope that will be watched. A global\n\t\t// range is forbidden for users that are intentionally limited to one or\n\t\t// more prefixes, even when this individual Key or prefix is readable.\n\t\trevisionOptions := []clientv3.OpOption{clientv3.WithCountOnly()}\n\t\tif scope == \"prefix\" {\n\t\t\trevisionOptions = append(revisionOptions, clientv3.WithRange(prefixEnd(key)))\n\t\t}","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/etcd-go/watch.go#L209-L245","documentation":"watchStart validates the 'scope' parameter before opening an etcd watch stream. Only 'key' (watch a single key) and 'prefix' (watch all keys under a prefix) are supported; any other value is rejected before a client is even acquired. This fails fast so an invalid scope never reaches the etcd server.","triggerScenarios":"Calling the watch start handler with params['scope'] set to anything other than 'key' or 'prefix', e.g. 'range', 'dir', or a typo like 'prefx'.","commonSituations":"Porting code from other watch APIs that use scope names like 'subtree' or 'watch_prefix'; dynamically building the scope from user input that was never validated; case mismatch like 'Key' or 'PREFIX'.","solutions":["Pass scope:'key' or scope:'prefix' exactly (lowercase)","Omit 'scope' entirely to use the default of 'key'","Validate user-supplied scope strings against the allowed set before calling"],"exampleFix":"// before\n{ \"key\": \"/cfg\", \"scope\": \"subtree\" }\n// after\n{ \"key\": \"/cfg\", \"scope\": \"prefix\" }","handlingStrategy":"validation","validationCode":"func validWatchScope(p map[string]json.RawMessage) bool {\n\ts, ok := p[\"scope\"]\n\tif !ok { return true }\n\tvar v string\n\tif json.Unmarshal(s, &v) != nil { return false }\n\treturn v == \"key\" || v == \"prefix\"\n}","typeGuard":"func isWatchScope(v string) bool { return v == \"key\" || v == \"prefix\" }","tryCatchPattern":"params := map[string]json.RawMessage{\"key\": keyJSON, \"scope\": scopeJSON}\nif !validWatchScope(params) { return fmt.Errorf(\"scope %q invalid; use key or prefix\", scope) }\nw, err := agent.handle(ctx, \"watch\", params)\nif err != nil && strings.Contains(err.Error(), \"ETCD_WATCH_SCOPE_INVALID\") { return err }","preventionTips":["Restrict scope values in your config schema to enum key|prefix","Normalize user input with strings.ToLower before passing","Write a table-driven test asserting only key/prefix are accepted"],"tags":["etcd","watch","validation","go"],"backgroundTag":"invalid-parameter-value","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}