{"record":{"id":"961367cbaaf5e121","repo":"nsqio/nsq","slug":"e-touch-failed","errorCode":"E_TOUCH_FAILED","errorMessage":"ID already in flight","messagePattern":"ID already in flight","errorType":"exception","errorClass":"ClientErr","httpStatus":null,"severity":"critical","filePath":"nsqd/channel.go","lineNumber":549,"sourceCode":"\nfunc (c *Channel) StartDeferredTimeout(msg *Message, timeout time.Duration) error {\n\tabsTs := time.Now().Add(timeout).UnixNano()\n\titem := &pqueue.Item{Value: msg, Priority: absTs}\n\terr := c.pushDeferredMessage(item)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.addToDeferredPQ(item)\n\treturn nil\n}\n\n// pushInFlightMessage atomically adds a message to the in-flight dictionary\nfunc (c *Channel) pushInFlightMessage(msg *Message) error {\n\tc.inFlightMutex.Lock()\n\t_, ok := c.inFlightMessages[msg.ID]\n\tif ok {\n\t\tc.inFlightMutex.Unlock()\n\t\treturn errors.New(\"ID already in flight\")\n\t}\n\tc.inFlightMessages[msg.ID] = msg\n\tc.inFlightMutex.Unlock()\n\treturn nil\n}\n\n// popInFlightMessage atomically removes a message from the in-flight dictionary\nfunc (c *Channel) popInFlightMessage(clientID int64, id MessageID) (*Message, error) {\n\tc.inFlightMutex.Lock()\n\tmsg, ok := c.inFlightMessages[id]\n\tif !ok {\n\t\tc.inFlightMutex.Unlock()\n\t\treturn nil, errors.New(\"ID not in flight\")\n\t}\n\tif msg.clientID != clientID {\n\t\tc.inFlightMutex.Unlock()\n\t\treturn nil, errors.New(\"client does not own message\")\n\t}","sourceCodeStart":531,"sourceCodeEnd":567,"githubUrl":"https://github.com/nsqio/nsq/blob/85cf10c09c6c3c86160d6f0eb156f62d0efc1648/nsqd/channel.go#L531-L567","documentation":"When nsqd's message pump delivers a message it registers it as in-flight via StartInFlightTimeout -> pushInFlightMessage, which errors with 'ID already in flight' if the MessageID already exists in the channel's inFlightMessages map. MessageIDs must be globally unique per nsqd; a duplicate means either two messages with colliding IDs (misconfigured shared --node-id across nsqd nodes feeding the same cluster via nsq_to_http-style tooling, or clock/sequence anomalies) or an API-level double registration. On this error the delivering goroutine exits (goto exit in messagePump), dropping that client connection; the same map conflict also makes later TOUCH attempts on the stale ID fail with E_TOUCH_FAILED.","triggerScenarios":"Two nsqd instances running with the same --node-id whose messages converge on one channel (message IDs embed the node id); embedding nsqd and calling channel.StartInFlightTimeout twice for the same msg; a message requeued while a copy of it is still in the in-flight map. Consumer-visible symptom: connection reset during receive or E_TOUCH_FAILED on TOUCH of the ghost entry.","commonSituations":"Cloning a VM/container including the data dir or a fixed --node-id and running both copies; running test and prod nsqd with id=0 on the same topic; message mirroring setups feeding one channel from two nsqds that share an ID.","solutions":["Give every nsqd in the fleet a unique --node-id in [0,1024) (the root cause of real-world ID collisions).","Restart the affected nsqd with the corrected id; in-flight/deferred state is rebuilt and consumers reconnect automatically.","If embedding nsqd in Go, never call StartInFlightMessage/StartInFlightTimeout twice for one message - the pump does it for you on delivery.","Audit for duplicated data directories (same disk dir mounted by two instances)."],"exampleFix":"# before (two nodes, same id)\nnsqd --node-id=0 ...   # host A\nnsqd --node-id=0 ...   # host B\n# after\nnsqd --node-id=1 ...   # host A\nnsqd --node-id=2 ...   # host B","handlingStrategy":"validation","validationCode":"// before joining a cluster, assert a unique node id\nif nodeID < 0 || nodeID >= 1024 {\n\treturn fmt.Errorf(\"node id %d out of range [0,1024)\", nodeID)\n}\nif !acquireNodeIDLease(nodeID) { // e.g. ectcd/zookeeper lock or CMDB record\n\treturn fmt.Errorf(\"node id %d already in use\", nodeID)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Assign --node-id from a central registry with ownership records; never bake one id into a golden image.","Monitor for it: repeated connection resets during delivery plus E_TOUCH_FAILED 'ID already in flight' patterns indicate id collisions.","When embedding nsqd, leave in-flight bookkeeping to the message pump; do not call StartInFlightTimeout manually."],"tags":["nsq","nsqd","protocol","message-id","configuration"],"backgroundTag":null,"analyzedSha":"85cf10c09c6c3c86160d6f0eb156f62d0efc1648","analyzedAt":"2026-08-16T00:53:05.009Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}