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
- 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.
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
- 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.
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
- Failed to fail workflow callback
- Failed to describe workflow
- Failed to stop workflow
- You must provide a KMS key via `kmsKey` when configuring `cu
- Cannot set both "logging.retention" and "logging.logGroup"
AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30).
Data as JSON: /api/errors/820374b220cd6464.
Report an issue: GitHub.