{"record":{"id":"c9bc76f8a4434b77","repo":"redis/node-redis","slug":"proxy-failed-to-send-smigrated-notification-m","errorCode":null,"errorMessage":"[Proxy] Failed to send SMIGRATED notification: ${migratedResult.error}","messagePattern":"\\[Proxy\\] Failed to send SMIGRATED notification: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/test-utils/lib/fault-injector/proxied-fault-injector-cluster.ts","lineNumber":142,"sourceCode":"\n    if (!destinationNode) {\n      throw new Error(`[Proxy] No destination node`);\n    }\n\n    const sMigratedNotification = buildSMigratedNotification([\n      {\n        targetNode: destinationNode,\n        slotRanges: slots,\n      },\n    ]);\n\n    const migratedResult = await this.proxyController.sendToClient(\n      connections[sourceNode.id][0],\n      sMigratedNotification\n    );\n\n    if (!migratedResult.success) {\n      throw new Error(\n        `[Proxy] Failed to send SMIGRATED notification: ${migratedResult.error}`\n      );\n    }\n\n    return {\n      status: \"success\",\n      error: null,\n      output: \"Migration completed!\",\n    };\n  }\n}\n\ninterface ProxyNode {\n  id: string;\n  host: string;\n  port: number;\n  proxyPort: number;\n}","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/test-utils/lib/fault-injector/proxied-fault-injector-cluster.ts#L124-L160","documentation":"Thrown by the proxied fault injector during the second phase of a simulated cluster slot migration: after the SMIGRATING push notification was sent and the 2-second maintenance delay elapsed, the proxy failed to deliver the SMIGRATED notification to the source node's client connection. The interpolated migratedResult.error carries the proxy server's own failure reason (a SendResult with success=false).","triggerScenarios":"Calling triggerAction({ type: 'migrate', parameters: { slot_migration, destination_type } }) on a ProxiedFaultInjectorClientForCluster when connections[sourceNode.id][0] — the connection used to deliver SMIGRATED — is closed, dropped, or no longer registered with the proxy between the SMIGRATING and SMIGRATED phases, or when the proxy controller's HTTP /send-to-client request itself fails.","commonSituations":"The cluster client disconnected or reconnected during the 2s simulated maintenance window; the proxy server was restarted or is unreachable; the connectionId resolved at migrate start went stale by the time SMIGRATED is sent; network interruption between the test process and the proxy controller HTTP endpoint.","solutions":["Confirm the proxy server is still running and reachable (proxyController.getStats()) before triggering the migrate action.","Ensure the cluster client stays connected across the 2-second maintenance delay — do not tear down or reconnect the client mid-migration.","Re-resolve the live connection id immediately before the SMIGRATED send: call proxyController.getConnections() and re-read connections[sourceNode.id][0] rather than reusing the id captured before the 2s wait.","Inspect the interpolated migratedResult.error text in the thrown message for the proxy-specific reason (unknown connection, write failure, timeout) and address that root cause."],"exampleFix":"// before — connection id captured once, reused after the 2s delay\nconst connId = connections[sourceNode.id][0];\nawait this.proxyController.sendToClient(connId, sMigratingNotification);\nawait setTimeout(2_000);\nconst migratedResult = await this.proxyController.sendToClient(connId, sMigratedNotification);\n\n// after — re-resolve the live connection id right before the SMIGRATED send\nconst fresh = await this.proxyController.getConnections();\nconst liveConnId = fresh[sourceNode.id]?.[0];\nif (!liveConnId) throw new Error('[Proxy] source connection gone before SMIGRATED');\nconst migratedResult = await this.proxyController.sendToClient(liveConnId, sMigratedNotification);","handlingStrategy":"retry","validationCode":"// Re-resolve the live connection id immediately before sending, and verify reachability\nconst fresh = await proxyController.getConnections();\nconst liveConnId = fresh[sourceNode.id]?.[0];\nif (!liveConnId) {\n  throw new Error(`No live connection for node ${sourceNode.id}; cannot send SMIGRATED`);\n}\n// optionally: await proxyController.getStats() to confirm the proxy is responsive","typeGuard":null,"tryCatchPattern":"try {\n  const migratedResult = await proxyController.sendToClient(liveConnId, sMigratedNotification);\n  if (!migratedResult.success) throw new Error(migratedResult.error);\n} catch (e) {\n  // Distinguish transient proxy/transport failure from a genuinely closed connection.\n  // Retry once after re-resolving connections; surface the proxy's reason to the test.\n  throw new Error(`SMIGRATED delivery failed for ${liveConnId}: ${(e as Error).message}`);\n}","preventionTips":["Keep the cluster client connected for the whole migrate window; do not reconnect between SMIGRATING and SMIGRATED.","Re-resolve connection ids from getConnections() right before each send rather than reusing a stale id.","Confirm the proxy server is up (getStats) before starting a migrate action.","Surface migratedResult.error in test diagnostics so the proxy-specific reason is visible."],"tags":["proxy","cluster","migration","test-utils","network"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}