anomalyco/sst · error · SucceedError

Failed to succeed workflow callback

Error message

Failed to succeed workflow callback

What it means

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.

Source

Thrown at sdk/js/src/aws/workflow.ts:437

  ): Promise<void> {
    const response = await awsFetch(
      "lambda",
      `/2025-12-01/durable-execution-callbacks/${encodeURIComponent(
        token,
      )}/succeed`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body:
          input.payload === undefined
            ? undefined
            : JSON.stringify(input.payload),
      },
      options,
    );
    if (!response.ok) throw new SucceedError(response);
  }

  /**
   * Send a failure result for a pending workflow callback.
   *
   * This is the equivalent to calling
   * [`SendDurableExecutionCallbackFailure`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackFailure.html).
   */
  export async function fail(
    token: string,
    input: FailInput,
    options?: Options,
  ): Promise<void> {
    const response = await awsFetch(
      "lambda",
      `/2025-12-01/durable-execution-callbacks/${encodeURIComponent(
        token,
      )}/fail`,

View on GitHub (pinned to a0bd20f762)

Solutions

  1. 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).
  2. Call `heartbeat(token)` periodically in long-running external jobs so the callback does not time out before you succeed it.
  3. Ensure each token is resolved exactly once — guard with a state store or dedupe key in the external worker.
  4. Verify IAM permissions for SendDurableExecutionCallbackSuccess and retry 429/5xx with backoff.
  5. Check payload size/serializability; keep payloads within Lambda durable-execution limits.

Example fix

// before
await succeed(token, { payload: result }); // throws if token already completed
// after
try {
  await succeed(token, { payload: result });
} catch (err) {
  if (err instanceof SucceedError && err.response.status >= 400 && err.response.status < 500) {
    console.warn("callback already resolved or expired", token);
    return; // idempotent: treat as delivered
  }
  throw err; // retry 5xx/429
}
Defensive patterns

Strategy: try-catch

Validate before calling

// guard before calling: token must be non-empty and result delivered at most once
if (!token) throw new Error("missing callback token");
if (deliveredTokens.has(token)) return; // local dedupe

Type guard

function isSucceedError(err: unknown): err is SucceedError {
  return err instanceof SucceedError && typeof err.response?.status === "number";
}

Try / catch

try {
  await succeed(token, { payload: result });
  deliveredTokens.add(token);
} catch (err) {
  if (err instanceof SucceedError && err.response.status >= 400 && err.response.status < 500) {
    console.warn("callback expired or already resolved:", token, err.response.status);
    return; // idempotent handling
  }
  throw err; // retry 429/5xx
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/820374b220cd6464. Report an issue: GitHub.