{"record":{"id":"b45d51888400db59","repo":"flowable/flowable-engine","slug":"can-only-terminate-cmmn-external-job-job-with-id","errorCode":null,"errorMessage":"Can only terminate CMMN external job. Job with id '${jobId}' is from scope '${scopeType}'","messagePattern":"Can only terminate CMMN external job\\. Job with id '(.+?)' is from scope '(.+?)'","errorType":"validation","errorClass":"FlowableIllegalArgumentException","httpStatus":400,"severity":"error","filePath":"modules/flowable-external-job-rest/src/main/java/org/flowable/external/job/rest/service/api/acquire/ExternalWorkerAcquireJobResource.java","lineNumber":218,"sourceCode":"\n        if (!workerId.equals(job.getLockOwner())) {\n            throw new FlowableForbiddenException(workerId + \" does not hold a lock on the requested job\");\n        }\n\n        if (ScopeTypes.CMMN.equals(job.getScopeType())) {\n            if (cmmnManagementService != null) {\n                if (restApiInterceptor != null) {\n                    restApiInterceptor.cmmnTerminateExternalWorkerJob(job, request);\n                }\n\n                cmmnManagementService.createCmmnExternalWorkerTransitionBuilder(job.getId(), workerId)\n                        .variables(extractVariables(request.getVariables()))\n                        .terminate();\n            } else {\n                throw new FlowableException(\"Cannot complete CMMN job. There is no CMMN engine available\");\n            }\n        } else {\n            throw new FlowableIllegalArgumentException(\n                    \"Can only terminate CMMN external job. Job with id '\" + jobId + \"' is from scope '\" + job.getScopeType() + \"'\");\n        }\n\n        return ResponseEntity.noContent().build();\n    }\n\n    @ApiOperation(value = \"Fail an External Worker Job\", code = 204, tags = { \"Acquire and Execute\" })\n    @ApiResponses({\n            @ApiResponse(code = 204, message = \"Indicates the job was successfully completed.\"),\n            @ApiResponse(code = 400, message = \"Indicates the request was invalid.\"),\n            @ApiResponse(code = 403, message = \"Indicates the user does not have the rights complete the job.\"),\n            @ApiResponse(code = 404, message = \"Indicates the job does not exist.\"),\n    })\n    @PostMapping(value = \"/acquire/jobs/{jobId}/fail\", produces = \"application/json\")\n    public ResponseEntity<?> failJob(@PathVariable String jobId, @RequestBody ExternalWorkerJobFailureRequest request) {\n        String workerId = request.getWorkerId();\n        if (StringUtils.isEmpty(workerId)) {\n            throw new FlowableIllegalArgumentException(\"workerId is required\");","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-external-job-rest/src/main/java/org/flowable/external/job/rest/service/api/acquire/ExternalWorkerAcquireJobResource.java#L200-L236","documentation":"FlowableIllegalArgumentException thrown when termination is requested for an external worker job whose scopeType is not CMMN (typically a BPMN process job). CMMN termination transitions apply only to CMMN case jobs; BPMN jobs must be completed or failed via their own endpoints. The message includes the offending job id and actual scope type.","triggerScenarios":"POST /acquire/jobs/{jobId}/cmmnTerminate with a jobId resolving to a job whose getScopeType() is not 'cmmn' (e.g. 'bpmn').","commonSituations":"Generic worker loops that call terminate for every failed job regardless of scope; mixed-engine deployments where scope type was not inspected; off-by-one/id-swap bugs in batch processing of acquired jobs.","solutions":["Inspect the acquired job's scopeType and branch: use failJob/bpmnError for BPMN jobs, cmmnTerminate for CMMN jobs.","Filter acquire requests or acquired job lists to CMMN jobs if the worker only handles cases.","Log the jobId and scopeType on failure to catch id-mixing bugs in worker loops."],"exampleFix":"// before\ntransitionBuilder.terminate();\n\n// after\nif (ScopeTypes.CMMN.equals(job.getScopeType())) {\n    transitionBuilder.terminate();\n} else {\n    managementService.createExternalWorkerCompletionBuilder(job.getId(), workerId).fail(errorMessage);\n}","handlingStrategy":"validation","validationCode":"if (job.scopeType !== 'cmmn') {\n  throw new Error('job ' + job.id + ' is scope ' + job.scopeType + '; cmmnTerminate is CMMN-only');\n}","typeGuard":"function isCmmnJob(job) { return job?.scopeType === 'cmmn'; }","tryCatchPattern":"try {\n  await api.cmmnTerminate(jobId, req);\n} catch (e) {\n  if (e.status === 400 && /scope/.test(e.message)) {\n    return routeByScope(jobId, req); // use fail/bpmnError for BPMN jobs\n  }\n  throw e;\n}","preventionTips":["Branch on scopeType before selecting the completion endpoint.","Never assume the scope of a job id in shared worker loops.","Log jobId + scopeType when dispatching to ease debugging."],"tags":["rest","cmmn","bpmn","scope-mismatch","external-worker"],"backgroundTag":"unsupported-operation","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}