{"record":{"id":"9f642ebfe7962777","repo":"floci-io/floci","slug":"customresourcefailed","errorCode":"CustomResourceFailed","errorMessage":"Custom resource handler reported FAILED: {}","messagePattern":"Custom resource handler reported FAILED: (.+?)","errorType":"exception","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java","lineNumber":4891,"sourceCode":"                ? ((ObjectNode) resolvedProps).deepCopy()\n                : objectMapper.createObjectNode();\n        ObjectNode resourceProperties = (ObjectNode) stringifyScalars(resolved);\n\n        boolean isUpdate = r.getPhysicalId() != null;\n        String requestType = isUpdate ? \"Update\" : \"Create\";\n        String priorPhysicalId = isUpdate ? r.getPhysicalId() : null;\n\n        // On Update, CloudFormation includes the previous ResourceProperties so the handler can diff.\n        // The prior values were stashed at the last create/update; read them before we overwrite below.\n        ObjectNode oldResourceProperties = isUpdate ? readStashedProperties(r) : null;\n\n        JsonNode response = invokeCustomResourceHandler(serviceToken, requestType, r.getLogicalId(),\n                r.getResourceType(), priorPhysicalId, resourceProperties, oldResourceProperties,\n                region, accountId, stackName);\n\n        String status = response.path(\"Status\").asText(\"FAILED\");\n        if (!\"SUCCESS\".equals(status)) {\n            throw new AwsException(\"CustomResourceFailed\",\n                    \"Custom resource handler reported FAILED: \"\n                            + response.path(\"Reason\").asText(\"(no reason given)\"), 400);\n        }\n\n        String returnedPhysicalId = response.path(\"PhysicalResourceId\").asText(null);\n        if (returnedPhysicalId != null && !returnedPhysicalId.isBlank()) {\n            r.setPhysicalId(returnedPhysicalId);\n        } else if (priorPhysicalId != null) {\n            r.setPhysicalId(priorPhysicalId);\n        } else {\n            r.setPhysicalId(r.getLogicalId() + \"-\" + UUID.randomUUID().toString().substring(0, 12));\n        }\n\n        // Data.* become Fn::GetAtt attributes on the custom resource.\n        JsonNode data = response.path(\"Data\");\n        if (data.isObject()) {\n            data.fields().forEachRemaining(e ->\n                    r.getAttributes().put(e.getKey(), nodeToAttributeValue(e.getValue())));","sourceCodeStart":4873,"sourceCodeEnd":4909,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java#L4873-L4909","documentation":"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.","triggerScenarios":"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)'.","commonSituations":"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.","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."],"exampleFix":"# before (handler)\ntry:\n    do_work(props)\n    cfnresponse.send(event, ctx, cfnresponse.FAILED, {}, reason='boom')\n# after\ntry:\n    do_work(props)\n    cfnresponse.send(event, ctx, cfnresponse.SUCCESS, {})\nexcept Exception as e:\n    cfnresponse.send(event, ctx, cfnresponse.FAILED, {}, reason=str(e))","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    cfn.executeChangeSet(...).get();\n} catch (CloudFormationException e) {\n    if (e.getMessage().contains(\"handler reported FAILED\")) {\n        log.error(\"custom resource FAILED: {}\", e.getMessage()); // Reason is embedded\n        dumpLambdaLogs(serviceTokenFromTemplate());\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["cloudformation","custom-resource","lambda","handler-failure"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}