{"record":{"id":"7a3383f8ee2f98b0","repo":"t8y2/dbx","slug":"etcd-watch-not-found","errorCode":"ETCD_WATCH_NOT_FOUND","errorMessage":"ETCD_WATCH_NOT_FOUND: watch does not exist","messagePattern":"ETCD_WATCH_NOT_FOUND: watch does not exist","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/etcd-go/watch.go","lineNumber":357,"sourceCode":"\t\trow[\"metadata\"] = metadataMap(item)\n\t}\n\treturn row\n}\n\nfunc eventType(event *clientv3.Event) string {\n\tif event.Type == mvccpb.DELETE {\n\t\treturn \"delete\"\n\t}\n\treturn \"put\"\n}\n\nfunc (s *etcdSession) watchPoll(params map[string]json.RawMessage) (any, error) {\n\twatchID := stringOrDefault(params, \"watchId\", \"\")\n\ts.watchesMu.Lock()\n\tstate := s.watches[watchID]\n\ts.watchesMu.Unlock()\n\tif state == nil {\n\t\treturn nil, errors.New(\"ETCD_WATCH_NOT_FOUND: watch does not exist\")\n\t}\n\tresult := state.poll()\n\tif _, terminal := result[\"terminal\"]; terminal {\n\t\tif removed := s.removeWatch(watchID); removed != nil {\n\t\t\tremoved.close()\n\t\t}\n\t}\n\treturn result, nil\n}\n\nfunc (s *etcdSession) watchStop(params map[string]json.RawMessage) (any, error) {\n\tif state := s.removeWatch(stringOrDefault(params, \"watchId\", \"\")); state != nil {\n\t\tstate.close()\n\t}\n\treturn map[string]bool{\"stopped\": true}, nil\n}\n","sourceCodeStart":339,"sourceCodeEnd":374,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/etcd-go/watch.go#L339-L374","documentation":"watchPoll looks up the watch state by 'watchId' in the session's watch registry. If no watch with that ID exists, polling cannot proceed and this error is thrown. A watch may be absent because it was never started, already terminated (terminal watches are auto-removed in watchPoll), or belongs to a different session.","triggerScenarios":"Calling watchPoll with a watchId that was never returned by watchStart, reusing a watchId after the watch reached a terminal result (it is removed on first terminal poll), or polling a watch created in a different session.","commonSituations":"Clients caching watch IDs after restarts; double-consuming a terminal watch event; race where two pollers race and one removes the watch first; typo'd or truncated ID.","solutions":["Use the watchId exactly as returned by watchStart","Treat this error as terminal: stop polling and start a new watch via watchStart","On session restart, discard old watch IDs and re-create watches","Guard against polling the same watch from two consumers"],"exampleFix":"// before\nresult := poll(oldWatchID) // removed after terminal event\n// after\nif isWatchNotFound(err) { state = startWatch(key, prefix) }","handlingStrategy":"try-catch","validationCode":"func (s *Session) hasWatch(id string) bool {\n\ts.watchesMu.Lock(); defer s.watchesMu.Unlock()\n\treturn s.watches[id] != nil\n}","typeGuard":null,"tryCatchPattern":"res, err := agent.handle(ctx, \"watchPoll\", map[string]json.RawMessage{\"watchId\": idJSON})\nif err != nil {\n\tif strings.Contains(err.Error(), \"ETCD_WATCH_NOT_FOUND\") {\n\t\tid = restartWatch(key, prefix) // recreate and resume\n\t\treturn\n\t}\n\treturn err\n}","preventionTips":["Persist watch IDs only per-session; discard after restart","Stop polling immediately after a terminal result (the watch is auto-removed)","Route all polls for one watch through a single consumer/goroutine","Log watch removals so stale IDs are detectable"],"tags":["etcd","watch","state","go"],"backgroundTag":"resource-not-found","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"}