{"record":{"id":"7365c43efbc329d1","repo":"apache/seatunnel","slug":"job-is-trying-to-leave-terminal-state-current","errorCode":null,"errorMessage":"Job is trying to leave terminal state ${current}","messagePattern":"Job is trying to leave terminal state (.+?)","errorType":"exception","errorClass":"SeaTunnelEngineException","httpStatus":null,"severity":"error","filePath":"seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/dag/physical/PhysicalPlan.java","lineNumber":334,"sourceCode":"                log.warn(\n                        \"{} current state is null, skip transition to {}\",\n                        jobFullName,\n                        targetState);\n                return;\n            }\n            log.debug(\n                    \"Try to update the {} state from {} to {}\", jobFullName, current, targetState);\n\n            if (current.equals(targetState)) {\n                log.info(\n                        \"{} current state equals target state: {}, skip\", jobFullName, targetState);\n                return;\n            }\n\n            // consistency check\n            if (current.isEndState()) {\n                String message = \"Job is trying to leave terminal state \" + current;\n                throw new SeaTunnelEngineException(message);\n            }\n\n            // Now do the actual state transition, we must update runningJobStateTimestampsIMap\n            // first and then can update runningJobStateIMap\n            updateStateInfo(current, targetState);\n            reportJobStateEvent(targetState);\n\n            stateProcess();\n        } catch (Exception e) {\n            log.error(ExceptionUtils.getMessage(e));\n            if (!targetState.equals(JobStatus.FAILING)) {\n                makeJobFailing(e);\n            }\n        }\n    }\n\n    public JobImmutableInformation getJobImmutableInformation() {\n        return jobImmutableInformation;","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/dag/physical/PhysicalPlan.java#L316-L352","documentation":"SeaTunnel Zeta engine throws this when a job state transition attempts to move a PhysicalPlan out of a terminal state (FINISHED, FAILED, CANCELED, etc.). Terminal states are final by design; the plan guards the transition inside updateJobState so that no stale callback or late state update can resurrect a finished job. It is a SeaTunnelEngineException, so it usually surfaces during coordinator operations like cancel or savepoint racing with job completion.","triggerScenarios":"Calling cancelJob/savepointJob/stopJob after the job already reached an end state; a pipeline-end callback firing after the plan was marked terminal; startJob on a plan whose state was already finalized by a prior run; concurrent state updates landing after terminal transition.","commonSituations":"User issues a cancel at the same moment the job finishes; retry/restart logic re-invokes startJob on an already-terminal plan; savepoint requested on a completed job; race between master node failover replay and job completion events.","solutions":["Check the job's current state via REST/CLI before issuing cancel, savepoint, or stop operations","Treat the exception as confirmation that the job already completed; query the final JobResult instead of retrying the transition","Fix client code to subscribe to job state events rather than polling and issuing duplicate lifecycle calls","If it occurs during cluster restart/failover replay, verify the runningJobStateIMap is consistent and no duplicated callbacks are registered"],"exampleFix":"// before\nif (jobMetrics.someCondition()) {\n    jobClient.savepoint(); // may race with completion\n}\n// after\nJobStatus s = jobClient.getJobStatus();\nif (!s.isEndState()) {\n    jobClient.savepoint();\n}","handlingStrategy":"try-catch","validationCode":"JobStatus s = jobClient.getJobStatus(jobId);\nif (s.isEndState()) { /* skip cancel/savepoint/stop */ }","typeGuard":"boolean isTerminal(JobStatus s) { return s.isEndState(); }","tryCatchPattern":"try { jobClient.cancelJob(jobId); } catch (SeaTunnelEngineException e) { if (e.getMessage().contains(\"leave terminal state\")) { /* job already finished; treat as no-op */ } else { throw e; } }","preventionTips":["Query job state before lifecycle operations","Subscribe to job state events instead of blind re-submission","Avoid issuing savepoint/cancel concurrently with expected completion"],"tags":["zeta-engine","job-state","state-machine","race-condition"],"backgroundTag":"invalid-state-transition","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}