{"record":{"id":"dca6c4fcf88daa18","repo":"anomalyco/sst","slug":"failed-to-stop-workflow","errorCode":null,"errorMessage":"Failed to stop workflow","messagePattern":"Failed to stop workflow","errorType":"error_code","errorClass":"StopError","httpStatus":null,"severity":"error","filePath":"sdk/js/src/aws/workflow.ts","lineNumber":396,"sourceCode":"  ): Promise<StopResponse> {\n    const response = await awsFetch(\n      \"lambda\",\n      `/2025-12-01/durable-executions/${encodeURIComponent(arn)}/stop`,\n      {\n        method: \"POST\",\n        headers: input?.error\n          ? {\n              \"Content-Type\": \"application/json\",\n            }\n          : undefined,\n        body:\n          input?.error === undefined\n            ? undefined\n            : JSON.stringify(normalizeError(input.error)),\n      },\n      options,\n    );\n    if (!response.ok) throw new StopError(response);\n\n    const data = (await response.json()) as StopInvocationResponse;\n    return {\n      arn,\n      status: \"STOPPED\",\n      stoppedAt:\n        data.StopTimestamp === undefined\n          ? undefined\n          : parseTimestamp(data.StopTimestamp),\n    };\n  }\n\n  /**\n   * Send a successful result for a pending workflow callback.\n   *\n   * This is the equivalent to calling\n   * [`SendDurableExecutionCallbackSuccess`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackSuccess.html).\n   */","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/sdk/js/src/aws/workflow.ts#L378-L414","documentation":"Thrown by the public `stop` function when the POST to `/2025-12-01/durable-executions/{arn}/stop` returns a non-OK HTTP status. It wraps the raw Response so the caller can inspect status and body. This means the execution was not stopped — it may still be running and consuming retries/time.","triggerScenarios":"Calling `Workflow.stop(arn)` where: (1) the ARN does not exist or was already completed (404/409 — execution already SUCCEEDED/FAILED/STOPPED); (2) credentials lack the stop permission (`lambda:StopDurableExecution`); (3) throttling (429) or transient 5xx; (4) malformed error payload in `input.error` rejected by the API.","commonSituations":"Race condition where the workflow finished before the stop call landed; cancelling stale executions from a cleanup script against the wrong region; IAM policy missing the durable-execution stop action; double-stopping the same execution from concurrent workers.","solutions":["Check the wrapped response status — 404/409 usually means the execution already terminated, which may be acceptable; treat idempotently.","Verify IAM permissions for stopping durable executions in the target region/account.","Retry 429/5xx with backoff before giving up.","Validate the optional `input.error` object matches the expected shape ({ errorType, errorMessage }) when passing it."],"exampleFix":"// before\nawait stop(arn); // throws if already stopped\n// after\ntry {\n  await stop(arn, { error: { errorType: \"Cancelled\", errorMessage: \"user cancelled\" } });\n} catch (err) {\n  if (err instanceof StopError && [404, 409].includes(err.response.status)) return; // already terminal\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"// check current state first; skip stop if already terminal\nconst current = await describe(arn).catch(() => null);\nif (!current || [\"SUCCEEDED\", \"FAILED\", \"STOPPED\"].includes(current.status)) {\n  return; // nothing to stop\n}","typeGuard":"function isStopError(err: unknown): err is StopError {\n  return err instanceof StopError && typeof err.response?.status === \"number\";\n}","tryCatchPattern":"try {\n  await stop(arn, { error: { errorType: \"Cancelled\", errorMessage: \"user requested stop\" } });\n} catch (err) {\n  if (err instanceof StopError && [404, 409].includes(err.response.status)) {\n    return; // already terminal — treat stop as idempotent\n  }\n  if (err instanceof StopError && (err.response.status === 429 || err.response.status >= 500)) {\n    return retryWithBackoff(() => stop(arn));\n  }\n  throw err;\n}","preventionTips":["Design stop flows to be idempotent — a 404/409 after stop usually means the execution finished first.","Pass a structured input.error ({ errorType, errorMessage }) so the API accepts the body.","Verify IAM includes the durable-execution stop action in the target region.","Avoid concurrent workers stopping the same execution without coordination."],"tags":["aws","http","lambda","durable-executions","stop","race-condition"],"backgroundTag":"aws-api-error-response","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}