{"record":{"id":"1feb42fb72c89f18","repo":"floci-io/floci","slug":"actiontypealreadyexistsexception","errorCode":"ActionTypeAlreadyExistsException","errorMessage":"Action type already exists","messagePattern":"Action type already exists","errorType":"exception","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/codepipeline/CodePipelineService.java","lineNumber":481,"sourceCode":"        putExecution(rollback);\n        return started;\n    }\n\n    private ObjectNode overrideStageCondition(JsonNode request, String region, String account) {\n        requireExecution(account, region, text(request, \"pipelineName\"), text(request, \"pipelineExecutionId\"));\n        return mapper.createObjectNode();\n    }\n\n    private ObjectNode listRuleExecutions(JsonNode request, String region, String account) {\n        requirePipeline(account, region, text(request, \"pipelineName\"));\n        return emptyPage(\"ruleExecutionDetails\");\n    }\n\n    private ObjectNode createCustomActionType(JsonNode request, String region, String account) {\n        String id = actionTypeId(request.path(\"category\").asText(), \"Custom\",\n                request.path(\"provider\").asText(), request.path(\"version\").asText());\n        if (itemStore.getForAccount(account, itemKey(region, \"action\", id)).isPresent()) {\n            throw new AwsException(\"ActionTypeAlreadyExistsException\", \"Action type already exists\", 400);\n        }\n        ObjectNode actionType = mapper.createObjectNode();\n        actionType.setAll((ObjectNode) request.deepCopy());\n        actionType.putObject(\"id\")\n                .put(\"category\", request.path(\"category\").asText())\n                .put(\"owner\", \"Custom\")\n                .put(\"provider\", request.path(\"provider\").asText())\n                .put(\"version\", request.path(\"version\").asText());\n        storeItem(account, region, \"action\", id, \"Active\", actionType);\n        return mapper.createObjectNode().set(\"actionType\", actionType);\n    }\n\n    private ObjectNode updateActionType(JsonNode request, String region, String account) {\n        JsonNode identifier = request.path(\"actionType\");\n        String id = actionTypeId(identifier.path(\"category\").asText(), identifier.path(\"owner\").asText(),\n                identifier.path(\"provider\").asText(), identifier.path(\"version\").asText());\n        CodePipelineStoredItem existing = requireItem(account, region, \"action\", id, \"ActionTypeNotFoundException\");\n        existing.setData(request.deepCopy());","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/codepipeline/CodePipelineService.java#L463-L499","documentation":"Thrown by createCustomActionType (CodePipelineService.java:481) when a custom action type with the same category+provider+version id already exists in the account store. The id string is the composite key, so any collision on those three fields — even with different settings — triggers ActionTypeAlreadyExistsException.","triggerScenarios":"CreateCustomActionType(category=X, provider=P, version=V) called twice; re-running bootstrap scripts that register the same custom action; two teams independently registering provider 'Jenkins' version '1'.","commonSituations":"Non-idempotent setup scripts run per CI job; environment rebuilds without cleanup; provider naming collisions across projects sharing one account.","solutions":["ListActionTypes first, filter owner=Custom, and skip creation when the id already exists.","Delete the stale custom action type before re-registering, if the semantics changed.","Make registration scripts idempotent (check-then-create) and use unique provider names per team."],"exampleFix":"// before\nclient.create_custom_action_type(category='Build', provider='Jenkins', version='1', ...)\n\n// after\nexisting = [a for a in client.list_action_types().get('actionTypes', [])\n             if a['id']['owner'] == 'Custom']\nhave = {(a['id']['category'], a['id']['provider'], a['id']['version']) for a in existing}\nif ('Build', 'Jenkins', '1') not in have:\n    client.create_custom_action_type(category='Build', provider='Jenkins', version='1', ...)","handlingStrategy":"validation","validationCode":"existing = {(a['id']['category'], a['id']['provider'], a['id']['version'])\n           for a in client.list_action_types().get('actionTypes', [])\n           if a['id']['owner'] == 'Custom'}\nif ('Build', 'MyProvider', '1') not in existing:\n    client.create_custom_action_type(category='Build', provider='MyProvider',\n                                     version='1', inputArtifactDetails=..., outputArtifactDetails=...)","typeGuard":null,"tryCatchPattern":"try:\n    client.create_custom_action_type(...)\nexcept ClientError as e:\n    if e.response['Error']['Code'] == 'ActionTypeAlreadyExistsException':\n        logger.info('custom action already registered; skipping')\n    else:\n        raise","preventionTips":["Make bootstrap scripts check-then-create instead of unconditional create.","Use team-unique provider names to avoid collisions in shared accounts.","Bump the version field when action semantics change instead of re-registering the same id."],"tags":["codepipeline","custom-action","idempotency","name-conflict","validation"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}