floci-io/floci · error · AwsException
CustomResourceTimeout
CustomResourceTimeout
Error message
Timed out waiting for custom resource {} to PUT its response to ResponseURL What it means
The custom resource Lambda invoke succeeded (no FunctionError), but the handler never PUT its response to the ResponseURL within CR_RESPONSE_TIMEOUT — customResourceResponseStore.await(token, ...) timed out. Floci surfaces this as CustomResourceTimeout with HTTP 504. Under the synchronous Pattern 1 design the response is expected immediately after the handler returns.
Source
Thrown at src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java:5066
if (oldResourceProperties != null) {
event.set("OldResourceProperties", oldResourceProperties);
}
byte[] payload = objectMapper.writeValueAsBytes(event);
InvokeResult result = lambdaService.invoke(region, serviceToken, payload,
InvocationType.RequestResponse);
if (result.getFunctionError() != null) {
String body = result.getPayload() != null
? new String(result.getPayload(), StandardCharsets.UTF_8) : "";
throw new AwsException("CustomResourceFailed",
"Custom resource handler errored (" + result.getFunctionError() + "): " + body, 400);
}
return customResourceResponseStore.await(token, CR_RESPONSE_TIMEOUT);
} catch (AwsException e) {
throw e;
} catch (TimeoutException e) {
throw new AwsException("CustomResourceTimeout",
"Timed out waiting for custom resource " + logicalId
+ " to PUT its response to ResponseURL", 504);
} catch (Exception e) {
throw new AwsException("CustomResourceFailed",
"Failed to invoke custom resource " + logicalId + ": " + e.getMessage(), 500);
}
}
private static String nodeToAttributeValue(JsonNode node) {
if (node == null || node.isNull()) {
return "";
}
return node.isValueNode() ? node.asText() : node.toString();
}
/**
* Mirrors CloudFormation's stringification of custom-resource ResourceProperties: every scalar
* (boolean, number, text) becomes a string, while object and array structure is preserved.View on GitHub (pinned to 62ff490619)
Solutions
- Make sure the handler PUTs to event['ResponseURL'] before it returns — in Node await the PUT, in Python call cfnresponse.send synchronously.
- Add/verify logging around the response PUT and check the Floci CfnResponseController received anything at all.
- Shorten or split the handler's work so it finishes within the configured timeout, or emit SUCCESS early only when work is truly done.
- Confirm the handler is Pattern 1 (single synchronous Lambda); async provider-framework handlers need rewriting or a synchronous wrapper.
Example fix
// before (Node): PUT fires after return, races the waiter
export const handler = async (event) => { doWork(event); sendResponse(event); }
// after
export const handler = async (event) => {
await doWork(event);
await sendResponse(event); // PUT completes before handler returns
} Defensive patterns
Strategy: retry
Try / catch
try {
stackOps.executeChangeSet(name, cs);
} catch (AwsException e) {
if ("CustomResourceTimeout".equals(e.getCode())) {
// transient: handler may have PUT just after the window — safe to re-run the change set
return retryWithBackoff();
}
throw e;
} Prevention
- Always await the ResponseURL PUT before the handler returns (async handler semantics).
- Keep custom resource work well under the emulator's CR_RESPONSE_TIMEOUT.
- Log immediately before the PUT so timeouts are diagnosable from function logs.
When it happens
Trigger: Handler returns 200 without calling cfnresponse.send / without PUTting to the ResponseURL; handler PUTs asynchronously after returning (deferred thread/callback) so the PUT races past the wait; handler took longer than the timeout before responding.
Common situations: Node handlers that respond via a callback after the main function returns without awaiting it; handlers written for the async Provider framework that skip the response PUT; long-running work exceeding CR_RESPONSE_TIMEOUT; response PUT sent to a wrong/rotated URL.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- CustomResourceFailed
- Override %s must not be blank.
- Hook script timed out after %d seconds: %s
- Failed to serialize proxy event
- Failed to serialize v1 authorizer event
AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14).
Data as JSON: /api/errors/8c149a0472e49bdc.
Report an issue: GitHub.