{"record":{"id":"9b6a4a413cfd5e9d","repo":"ruvnet/ruflo","slug":"subnet-subnet-too-small-for-mesh-ips","errorCode":null,"errorMessage":"subnet ${subnet} too small for mesh IPs","messagePattern":"subnet (.+?) too small for mesh IPs","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/plugin-agent-federation/src/domain/value-objects/wg-config.ts","lineNumber":119,"sourceCode":" *\n * Strategy: sha256(nodeId) → top bytes interpreted as host portion of the\n * subnet, clamped to avoid the network address (.0) and broadcast (.255)\n * of any /24 inside the subnet. This is collision-resistant in the\n * birthday-paradox sense — see ADR for thresholds.\n *\n * `usedIPs` (when provided) gives previously-assigned IPs in the mesh.\n * If the derived IP is already taken, the function probes `nodeId + '\\x00'`,\n * `nodeId + '\\x01'`, … until a free slot is found. Bounded to 1024 probes;\n * exceeding that means the subnet is functionally exhausted and the caller\n * should jump to a larger range (see ADR `10.50.0.0/12` recommendation).\n */\nexport function deriveMeshIP(\n  nodeId: string,\n  subnet: string = DEFAULT_MESH_SUBNET,\n  usedIPs: ReadonlySet<string> = new Set(),\n): string {\n  const { base, prefix } = parseCidr(subnet);\n  if (prefix > 30) throw new Error(`subnet ${subnet} too small for mesh IPs`);\n  const hostBits = 32 - prefix;\n  const hostMask = hostBits >= 32 ? 0xffffffff : ((1 << hostBits) - 1) >>> 0;\n  const networkBase = (base & ~hostMask) >>> 0;\n\n  for (let probe = 0; probe < 1024; probe++) {\n    const input = probe === 0 ? nodeId : `${nodeId}\\x00${probe}`;\n    const hash = createHash('sha256').update(input).digest();\n    // Use the first 4 bytes as the host portion.\n    const hashHost = ((hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3]) >>> 0;\n    let candidate = (networkBase | (hashHost & hostMask)) >>> 0;\n    const lastOctet = candidate & 0xff;\n    // Avoid .0 (network) and .255 (broadcast) of each /24 slice. Cheap\n    // clamp: bump to .1 if landing on .0, or .254 if landing on .255.\n    if (lastOctet === 0) candidate = (candidate | 1) >>> 0;\n    else if (lastOctet === 255) candidate = (candidate & ~1) >>> 0;\n    const ip = `${ipToString(candidate)}/32`;\n    if (!usedIPs.has(ip)) return ip;\n  }","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/plugin-agent-federation/src/domain/value-objects/wg-config.ts#L101-L137","documentation":"deriveMeshIP rejects the configured mesh subnet because after removing the network and broadcast addresses of each /24 it cannot hold the mesh IPs required — the address space is too small to assign deterministic collision-resistant host addresses to peers.","triggerScenarios":"The configured mesh subnet does not have enough address space to assign mesh IPs.","commonSituations":"Subnet like /30 or smaller chosen for a mesh that needs host addresses.","solutions":["Choose a wider subnet (e.g. /24) that fits the expected number of mesh nodes.","Reduce the mesh size or split into multiple subnets."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}