{"record":{"id":"35304a0c94615cfc","repo":"redis/node-redis","slug":"timeout-waiting-for-action-actionid","errorCode":null,"errorMessage":"Timeout waiting for action ${actionId}","messagePattern":"Timeout waiting for action (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/test-utils/lib/fault-injector/fault-injector-client.ts","lineNumber":130,"sourceCode":"\n    while (Date.now() - startTime < maxWaitTime) {\n      const action = await this.getActionStatus<ActionStatus>(actionId);\n\n      if (action.status === \"failed\") {\n        throw new Error(\n          `Action id: ${actionId} failed! Error: ${action.error}`\n        );\n      }\n\n      if ([\"finished\", \"success\"].includes(action.status)) {\n        dbg(`${actionId} completed: ${action.status}`)\n        return action;\n      }\n\n      await setTimeout(timeout);\n    }\n\n    throw new Error(`Timeout waiting for action ${actionId}`);\n  }\n\n  async migrateAndBindAction({\n    bdbId,\n    clusterIndex,\n  }: {\n    bdbId?: string | number;\n    clusterIndex: string | number;\n  }) {\n    const resolvedBdbId = this.#resolveBdbId(bdbId);\n    dbg('migrateAndBind: bdb', resolvedBdbId, 'cluster', clusterIndex);\n    const clusterIndexStr = clusterIndex.toString();\n\n    return this.triggerAction<{\n      action_id: string;\n    }>({\n      type: \"sequence_of_actions\",\n      parameters: {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/test-utils/lib/fault-injector/fault-injector-client.ts#L112-L148","documentation":"waitForAction loops until Date.now() - startTime exceeds maxWaitTimeMs (default 60000ms). If the action never reaches 'failed', 'finished', or 'success' within the window, it throws. Note the 'failed' check runs first, so this error specifically means the action stayed pending/running the whole time.","triggerScenarios":"triggerAction(...) where the backend accepts the action but never completes it: stuck queue, backend hang, a long-running operation exceeding 60s, or network stalls that make status polling return the same non-terminal state.","commonSituations":"Large migration on a slow cluster; backend overloaded; action genuinely takes longer than the default budget; polling degraded by intermittent fetch failures returning stale status.","solutions":["Increase the budget: triggerAction(action, { maxWaitTimeMs: 180_000 })","Call getActionStatus(actionId) after the timeout to see whether it eventually completes","Check fault-injector service health/logs for a stuck queue or hung worker"],"exampleFix":"// before — 60s default budget exceeded\nawait fi.triggerAction(action);\n\n// after — allow 3 minutes\nawait fi.triggerAction(action, { maxWaitTimeMs: 180_000, timeoutMs: 2_000 });","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await fi.triggerAction(action, { maxWaitTimeMs: 180_000 });\n} catch (e) {\n  if (e instanceof Error && /Timeout waiting for action/.test(e.message)) {\n    const status = await fi.getActionStatus(actionId);\n  }\n  throw e;\n}","preventionTips":["Pass an explicit maxWaitTimeMs sized to the expected operation duration","After a timeout, call getActionStatus to check whether the action eventually completes","Monitor fault-injector service health for stuck queues"],"tags":["fault-injection","timeout","test-utils","polling","action-status"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}