{"record":{"id":"820374b220cd6464","repo":"anomalyco/sst","slug":"failed-to-succeed-workflow-callback","errorCode":null,"errorMessage":"Failed to succeed workflow callback","messagePattern":"Failed to succeed workflow callback","errorType":"error_code","errorClass":"SucceedError","httpStatus":null,"severity":"error","filePath":"sdk/js/src/aws/workflow.ts","lineNumber":437,"sourceCode":"  ): Promise<void> {\n    const response = await awsFetch(\n      \"lambda\",\n      `/2025-12-01/durable-execution-callbacks/${encodeURIComponent(\n        token,\n      )}/succeed`,\n      {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body:\n          input.payload === undefined\n            ? undefined\n            : JSON.stringify(input.payload),\n      },\n      options,\n    );\n    if (!response.ok) throw new SucceedError(response);\n  }\n\n  /**\n   * Send a failure result for a pending workflow callback.\n   *\n   * This is the equivalent to calling\n   * [`SendDurableExecutionCallbackFailure`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackFailure.html).\n   */\n  export async function fail(\n    token: string,\n    input: FailInput,\n    options?: Options,\n  ): Promise<void> {\n    const response = await awsFetch(\n      \"lambda\",\n      `/2025-12-01/durable-execution-callbacks/${encodeURIComponent(\n        token,\n      )}/fail`,","sourceCodeStart":419,"sourceCodeEnd":455,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/sdk/js/src/aws/workflow.ts#L419-L455","documentation":"Thrown by the public `succeed` callback function when the POST to `/2025-12-01/durable-execution-callbacks/{token}/succeed` returns a non-OK HTTP status. It wraps the raw Response. The workflow remains waiting on the callback — the success result was not delivered, so the execution will keep waiting until it times out.","triggerScenarios":"Calling `Workflow.succeed(token)` where: (1) the callback token is expired, already used, or invalid (token reused after a prior succeed/fail call, or callback timed out); (2) the parent execution was stopped or deleted; (3) credentials lack the `lambda:SendDurableExecutionCallbackSuccess` permission; (4) throttling/transient 5xx; (5) payload too large or not JSON-serializable.","commonSituations":"External job workers delivering results after the callback timeout elapsed; retry logic double-sending success after a network blip caused the first call to land; tokens copied from stale queue messages; long-running jobs exceeding the callback heartbeat window without calling heartbeat.","solutions":["Check the wrapped response status — 4xx on the token usually means it expired, was already completed, or the execution is gone; treat duplicate delivery as success (idempotent handling).","Call `heartbeat(token)` periodically in long-running external jobs so the callback does not time out before you succeed it.","Ensure each token is resolved exactly once — guard with a state store or dedupe key in the external worker.","Verify IAM permissions for SendDurableExecutionCallbackSuccess and retry 429/5xx with backoff.","Check payload size/serializability; keep payloads within Lambda durable-execution limits."],"exampleFix":"// before\nawait succeed(token, { payload: result }); // throws if token already completed\n// after\ntry {\n  await succeed(token, { payload: result });\n} catch (err) {\n  if (err instanceof SucceedError && err.response.status >= 400 && err.response.status < 500) {\n    console.warn(\"callback already resolved or expired\", token);\n    return; // idempotent: treat as delivered\n  }\n  throw err; // retry 5xx/429\n}","handlingStrategy":"try-catch","validationCode":"// guard before calling: token must be non-empty and result delivered at most once\nif (!token) throw new Error(\"missing callback token\");\nif (deliveredTokens.has(token)) return; // local dedupe","typeGuard":"function isSucceedError(err: unknown): err is SucceedError {\n  return err instanceof SucceedError && typeof err.response?.status === \"number\";\n}","tryCatchPattern":"try {\n  await succeed(token, { payload: result });\n  deliveredTokens.add(token);\n} catch (err) {\n  if (err instanceof SucceedError && err.response.status >= 400 && err.response.status < 500) {\n    console.warn(\"callback expired or already resolved:\", token, err.response.status);\n    return; // idempotent handling\n  }\n  throw err; // retry 429/5xx\n}","preventionTips":["Call heartbeat(token) on a timer during long-running external work to prevent callback timeout.","Deliver each token exactly once — use a durable dedupe record in the external worker.","Keep payloads small and JSON-serializable.","Grant lambda:SendDurableExecutionCallbackSuccess to the calling identity."],"tags":["aws","http","lambda","callback","durable-executions","token-expired"],"backgroundTag":"callback-token-invalid-or-expired","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}