floci-io/floci · error · AwsException
CustomResourceFailed
CustomResourceFailed
Error message
Custom resource handler reported FAILED: {} What it means
The custom resource's backing Lambda ran successfully at the HTTP level, but its response (captured via the ResponseURL PUT that Floci's CfnResponseController records) carried Status=FAILED or no Status at all (default FAILED). Floci only accepts SUCCESS, so the stack operation fails with the handler-supplied Reason. Only single-Lambda synchronous handlers (Pattern 1, e.g. CDK BucketDeployment) are emulated.
Source
Thrown at src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java:4891
? ((ObjectNode) resolvedProps).deepCopy()
: objectMapper.createObjectNode();
ObjectNode resourceProperties = (ObjectNode) stringifyScalars(resolved);
boolean isUpdate = r.getPhysicalId() != null;
String requestType = isUpdate ? "Update" : "Create";
String priorPhysicalId = isUpdate ? r.getPhysicalId() : null;
// On Update, CloudFormation includes the previous ResourceProperties so the handler can diff.
// The prior values were stashed at the last create/update; read them before we overwrite below.
ObjectNode oldResourceProperties = isUpdate ? readStashedProperties(r) : null;
JsonNode response = invokeCustomResourceHandler(serviceToken, requestType, r.getLogicalId(),
r.getResourceType(), priorPhysicalId, resourceProperties, oldResourceProperties,
region, accountId, stackName);
String status = response.path("Status").asText("FAILED");
if (!"SUCCESS".equals(status)) {
throw new AwsException("CustomResourceFailed",
"Custom resource handler reported FAILED: "
+ response.path("Reason").asText("(no reason given)"), 400);
}
String returnedPhysicalId = response.path("PhysicalResourceId").asText(null);
if (returnedPhysicalId != null && !returnedPhysicalId.isBlank()) {
r.setPhysicalId(returnedPhysicalId);
} else if (priorPhysicalId != null) {
r.setPhysicalId(priorPhysicalId);
} else {
r.setPhysicalId(r.getLogicalId() + "-" + UUID.randomUUID().toString().substring(0, 12));
}
// Data.* become Fn::GetAtt attributes on the custom resource.
JsonNode data = response.path("Data");
if (data.isObject()) {
data.fields().forEachRemaining(e ->
r.getAttributes().put(e.getKey(), nodeToAttributeValue(e.getValue())));View on GitHub (pinned to 62ff490619)
Solutions
- Read the Reason in the error message — it is the handler's own failure text and usually names the root cause.
- Invoke the backing Lambda directly with a synthetic CloudFormation event payload (aws lambda invoke) and inspect its logs to reproduce outside the stack flow.
- Confirm the handler follows Pattern 1: it PUTs to ResponseURL (via cfnresponse or https.put) and does not rely on the async Provider framework's isComplete polling.
- Check the Lambda's permissions/inputs (buckets, parameters, secrets) inside the emulator; missing permission is the most common FAILED reason.
Example fix
# before (handler)
try:
do_work(props)
cfnresponse.send(event, ctx, cfnresponse.FAILED, {}, reason='boom')
# after
try:
do_work(props)
cfnresponse.send(event, ctx, cfnresponse.SUCCESS, {})
except Exception as e:
cfnresponse.send(event, ctx, cfnresponse.FAILED, {}, reason=str(e)) Defensive patterns
Strategy: try-catch
Try / catch
try {
cfn.executeChangeSet(...).get();
} catch (CloudFormationException e) {
if (e.getMessage().contains("handler reported FAILED")) {
log.error("custom resource FAILED: {}", e.getMessage()); // Reason is embedded
dumpLambdaLogs(serviceTokenFromTemplate());
}
throw e;
} Prevention
- Wrap handler bodies in try/except that send FAILED with a meaningful Reason — you will read it here.
- Smoke-test handlers with a synthetic event before deploying the stack.
- Give the handler's role the exact permissions it needs; permission errors are the top FAILED cause.
When it happens
Trigger: Handler code explicitly cfnresponse.send(event, context, FAILED, ...) because a pre-condition failed; handler threw and its wrapper caught it and sent FAILED; handler never PUT a response, in which case Status defaults to FAILED with '(no reason given)'.
Common situations: CDK BucketDeployment whose asset bucket/keys are unreachable; handler missing IAM permissions in the emulator; handler expecting Provider-framework event fields (RequestType onEvent/isComplete) it does not get under Pattern 1 emulation; runtime bug in user Lambda code.
Related errors
- CustomResourceTimeout
- Override %s must not be blank.
- Failed to serialize proxy event
- Failed to serialize v1 authorizer event
- InternalFailure
AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14).
Data as JSON: /api/errors/9f642ebfe7962777.
Report an issue: GitHub.