{"record":{"id":"82bbeb45a3b2737c","repo":"floci-io/floci","slug":"validationexception-82bbeb","errorCode":"ValidationException","errorMessage":"PollForThirdPartyJobs requires owner ThirdParty","messagePattern":"PollForThirdPartyJobs requires owner ThirdParty","errorType":"exception","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/codepipeline/CodePipelineService.java","lineNumber":540,"sourceCode":"        String ownerFilter = request.path(\"actionOwnerFilter\").asText(null);\n        if (ownerFilter != null) {\n            items = items.stream()\n                    .filter(i -> ownerFilter.equals(i.getData().path(\"id\").path(\"owner\").asText(\"Custom\")))\n                    .toList();\n        }\n        Page page = page(request, items.size(), 100);\n        ObjectNode response = mapper.createObjectNode();\n        ArrayNode actionTypes = response.putArray(\"actionTypes\");\n        items.subList(page.start(), page.end()).forEach(i -> actionTypes.add(i.getData()));\n        addNextToken(response, page, items.size());\n        return response;\n    }\n\n    private ObjectNode pollForJobs(JsonNode request, String region, String account, boolean thirdParty) {\n        JsonNode actionTypeId = request.path(\"actionTypeId\");\n        String owner = actionTypeId.path(\"owner\").asText();\n        if ((!thirdParty && !\"Custom\".equals(owner)) || (thirdParty && !\"ThirdParty\".equals(owner))) {\n            throw new AwsException(\"ValidationException\",\n                    thirdParty ? \"PollForThirdPartyJobs requires owner ThirdParty\"\n                            : \"PollForJobs requires owner Custom\", 400);\n        }\n        String requested = actionTypeId(actionTypeId.path(\"category\").asText(),\n                owner, actionTypeId.path(\"provider\").asText(),\n                actionTypeId.path(\"version\").asText());\n        ArrayNode jobs = mapper.createArrayNode();\n        int maximum = Math.max(1, request.path(\"maxBatchSize\").asInt(1));\n        for (CodePipelineStoredItem item : items(account, region, \"job\")) {\n            if (jobs.size() >= maximum) {\n                break;\n            }\n            if (!\"Created\".equals(item.getStatus())) {\n                continue;\n            }\n            JsonNode data = item.getData();\n            if (requested.equals(data.path(\"actionTypeKey\").asText())\n                    && thirdParty == data.path(\"thirdParty\").asBoolean(false)) {","sourceCodeStart":522,"sourceCodeEnd":558,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/codepipeline/CodePipelineService.java#L522-L558","documentation":"Thrown by pollForJobs (CodePipelineService.java:540) when the actionTypeId.owner does not match the polling API: PollForJobs requires owner 'Custom' and PollForThirdPartyJobs requires owner 'ThirdParty'. Any other owner value (AWS, e.g.) fails the guard with ValidationException before any jobs are returned.","triggerScenarios":"Calling PollForJobs with actionTypeId={'owner': 'ThirdParty', ...} or PollForThirdPartyJobs with owner 'Custom'/'AWS'; copy-pasting a job-worker config between a custom-action worker and a third-party worker.","commonSituations":"Job workers configured from templates with the wrong owner string; swapping poll endpoints when migrating a provider from custom to third-party mode; owner left as the default 'AWS' from an SDK example struct.","solutions":["Set owner='Custom' when calling PollForJobs and owner='ThirdParty' when calling PollForThirdPartyJobs.","Assert the owner value against the poll method name in worker startup code.","Remember the corresponding action type must have been created with the same owner (CreateCustomActionType implies Custom)."],"exampleFix":"// before\nclient.poll_for_jobs(actionTypeId={\n    'category': 'Build', 'owner': 'ThirdParty', 'provider': 'MyProvider', 'version': '1'})\n\n// after\nclient.poll_for_jobs(actionTypeId={\n    'category': 'Build', 'owner': 'Custom', 'provider': 'MyProvider', 'version': '1'})","handlingStrategy":"validation","validationCode":"REQUIRED_OWNER = {'poll_for_jobs': 'Custom',\n                 'poll_for_third_party_jobs': 'ThirdParty'}\n\ndef check_owner(method: str, action_type_id: dict) -> None:\n    want = REQUIRED_OWNER[method]\n    assert action_type_id.get('owner') == want, \\\n        f'{method} requires actionTypeId.owner == {want!r}'\n\ncheck_owner('poll_for_jobs', action_type_id)\nclient.poll_for_jobs(actionTypeId=action_type_id)","typeGuard":"def owner_matches_poll(method: str, action_type_id: dict) -> bool:\n    want = 'ThirdParty' if 'third_party' in method else 'Custom'\n    return action_type_id.get('owner') == want","tryCatchPattern":"try:\n    client.poll_for_third_party_jobs(actionTypeId=action_type_id)\nexcept ClientError as e:\n    if e.response['Error']['Code'] == 'ValidationException' and 'ThirdParty' in str(e):\n        action_type_id = {**action_type_id, 'owner': 'ThirdParty'}\n        client.poll_for_third_party_jobs(actionTypeId=action_type_id)\n    else:\n        raise","preventionTips":["Keep one config field for worker owner and derive the correct poll API from it.","Register the action type with the same owner the worker polls with.","Add a startup self-check in job workers that validates owner against the poll method."],"tags":["codepipeline","poll-jobs","worker","owner","validation"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}