{"record":{"id":"ca685e2155187e20","repo":"XTLS/Xray-core","slug":"ambiguous-process-match-for-connection-from-t","errorCode":null,"errorMessage":"ambiguous process match for  connection from ::: to :","messagePattern":"ambiguous process match for  connection from ::: to :","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"common/net/find_process_darwin.go","lineNumber":121,"sourceCode":"\t\t\tambiguousBest = false\n\t\t\tbreak\n\t\t}\n\t\tif matchLevel > bestLevel {\n\t\t\tbestPID = pid\n\t\t\tbestLevel = matchLevel\n\t\t\tambiguousBest = false\n\t\t\tcontinue\n\t\t}\n\t\tif matchLevel == bestLevel {\n\t\t\tambiguousBest = true\n\t\t}\n\t}\n\n\tif bestLevel == darwinSocketNoMatch {\n\t\treturn 0, \"\", \"\", errors.New(\"process not found for \", network, \" connection from \", srcIP, \":\", srcPort, \" to \", destIP, \":\", destPort)\n\t}\n\tif ambiguousBest {\n\t\treturn 0, \"\", \"\", errors.New(\"ambiguous process match for \", network, \" connection from \", srcIP, \":\", srcPort, \" to \", destIP, \":\", destPort)\n\t}\n\n\tabsPath, err := darwinProcessPath(bestPID)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"could not get process path for PID \", bestPID, \": \", err)\n\t}\n\n\tabsPath = filepath.ToSlash(absPath)\n\treturn int(bestPID), filepath.Base(absPath), absPath, nil\n}\n\nfunc darwinProcessSocketMatchLevel(pid int32, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) (darwinSocketMatchLevel, error) {\n\tfds, err := darwinProcessFDs(pid)\n\tif err != nil {\n\t\treturn darwinSocketNoMatch, err\n\t}\n\n\tbestLevel := darwinSocketNoMatch","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/common/net/find_process_darwin.go#L103-L139","documentation":"On macOS, two or more processes had sockets matching the queried connection at the same best match level, so the winner is ambiguous. The function refuses to guess and returns this error instead of a possibly wrong PID. It is a correctness guard against mis-attributing a connection.","triggerScenarios":"A forked process sharing an inherited socket with its parent (both FD tables reference the same socket), or two processes with identical local address:port bindings (SO_REUSEPORT, e.g. launchd workers). Both then tie at the same darwinSocketMatchLevel.","commonSituations":"Apps that fork workers holding inherited listeners (nginx-style prefork), macOS system daemons using SO_REUSEPORT, and containerized/multi-process Go apps that pass FDs to children.","solutions":["Do not treat ambiguity as fatal: fall back to non-process routing rules","If you control the target app, avoid passing listener FDs to forked children or set SO_REUSEPORT off","Pass destIP/destPort to the call; when hasDstAddr is true the matcher can distinguish directions and often break the tie","Report both candidate PIDs is not supported by the API, so design the caller to tolerate an unresolved PID"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"func isAmbiguousProcessMatch(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"ambiguous process match\")\n}","tryCatchPattern":"if err != nil && isAmbiguousProcessMatch(err) {\n    // two PIDs tied (forked/inherited socket); do not guess\n    return routeWithoutProcess(ctx)\n}","preventionTips":["Pass destIP and destPort — destination-aware matching breaks many ties","Avoid process rules for apps known to fork with inherited listeners","Never assume the error implies a specific PID; the API refuses to choose by design"],"tags":["darwin","process-lookup","ambiguous-match","fork"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}