{"record":{"id":"5986b57dd6dcbe84","repo":"t8y2/dbx","slug":"etcd-watch-not-found-5986b5","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/etcd2-go/watch.go","lineNumber":379,"sourceCode":"\tif action == \"delete\" || action == \"expire\" || action == \"compareAndDelete\" {\n\t\treturn \"delete\"\n\t}\n\treturn \"put\"\n}\n\nfunc (w *watchState) hasTerminal() bool {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\treturn w.terminalReason != \"\"\n}\n\nfunc (s *etcd2Session) 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 *etcd2Session) 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":361,"sourceCodeEnd":396,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/etcd2-go/watch.go#L361-L396","documentation":"watchPoll is called with a watchId that has no entry in the session's watches map, so there is nothing to poll. This happens when the watch was never started, already terminated (terminal results remove the watch), or the session was reused after the watch was cleaned up. The library deletes watches from its map once they return a terminal event, making subsequent polls invalid.","triggerScenarios":"Polling a watchId after a previous poll returned a terminal result; polling an id from a different or restarted session; passing an empty watchId (default of stringOrDefault) when the caller never stored the id returned by watchStart; a race where two pollers hit the same watch and one removes it.","commonSituations":"Client code caching watch ids across reconnects, retry loops that keep polling after the watch ended, and single-shot pollers that do not expect watch lifecycle termination.","solutions":["Treat the watch as ended: re-create it with watchStart and use the fresh watchId.","Stop polling after any poll result containing 'terminal' — the library removes the watch automatically.","Check that the watchId you pass is the exact id returned by watchStart from the same session.","Guard against empty watchId: only poll when the start call succeeded and you persisted its id."],"exampleFix":"// before\nres, _ := session.watchPoll(map[string]any{\"watchId\": cachedID})\n// after\nres, err := session.watchPoll(map[string]any{\"watchId\": currentID})\nif err != nil {\n    currentID = restartWatch(params) // watch no longer exists; recreate it\n}","handlingStrategy":"retry","validationCode":"func canPoll(id string) error {\n    if id == \"\" { return errors.New(\"empty watchId\") }\n    return nil\n}","typeGuard":"func hasWatchID(params map[string]json.RawMessage) bool {\n    var id string\n    return json.Unmarshal(params[\"watchId\"], &id) == nil && id != \"\"\n}","tryCatchPattern":"res, err := session.watchPoll(params)\nif err != nil && strings.Contains(err.Error(), \"ETCD_WATCH_NOT_FOUND\") {\n    currentID, err = session.watchStart(watchParams) // recreate and retry\n}","preventionTips":["Stop polling once a result contains \"terminal\"","Never cache watchIds across session restarts","Always persist the id returned by watchStart immediately"],"tags":["etcd","watch","state","lifecycle"],"backgroundTag":"watch-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"}