{"record":{"id":"d94b9d424c7bf69b","repo":"apache/seatunnel","slug":"invalid-state-currentsplitid-is-null-when-finishi","errorCode":null,"errorMessage":"Invalid state: currentSplitId is null when finishing snapshot split. emittedFinishedSplitId=%s, currentFetcher=%s, isFinished=%s","messagePattern":"Invalid state: currentSplitId is null when finishing snapshot split\\. emittedFinishedSplitId=(.+?), currentFetcher=(.+?), isFinished=(.+?)","errorType":"exception","errorClass":"java.io.IOException","httpStatus":null,"severity":"critical","filePath":"seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceSplitReader.java","lineNumber":207,"sourceCode":"\n    public boolean canAssignNextSplit() {\n        return currentFetcher == null || currentFetcher.isFinished();\n    }\n\n    private boolean hasEmittedCurrentSplitFinished() {\n        return currentSplitId != null && currentSplitId.equals(emittedFinishedSplitId);\n    }\n\n    private RecordsWithSplitIds<SourceRecords> finishedSnapshotSplit() throws IOException {\n        final String splitId = currentSplitId;\n        if (splitId == null) {\n            log.warn(\n                    \"Invalid state: currentSplitId is null when finishing snapshot split. \"\n                            + \"emittedFinishedSplitId={}, currentFetcher={}, isFinished={}\",\n                    emittedFinishedSplitId,\n                    currentFetcher != null ? currentFetcher.getClass().getSimpleName() : \"null\",\n                    currentFetcher != null && currentFetcher.isFinished());\n            throw new IOException(\n                    String.format(\n                            \"Invalid state: currentSplitId is null when finishing snapshot split. \"\n                                    + \"emittedFinishedSplitId=%s, currentFetcher=%s, isFinished=%s\",\n                            emittedFinishedSplitId,\n                            currentFetcher != null\n                                    ? currentFetcher.getClass().getSimpleName()\n                                    : \"null\",\n                            currentFetcher != null && currentFetcher.isFinished()));\n        }\n        if (splitId.equals(emittedFinishedSplitId)) {\n            return NoSplitRecords.INSTANCE;\n        }\n        emittedFinishedSplitId = splitId;\n        return ChangeEventRecords.forFinishedSplit(splitId);\n    }\n\n    private static final class NoSplitRecords implements RecordsWithSplitIds<SourceRecords> {\n        private static final NoSplitRecords INSTANCE = new NoSplitRecords();","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceSplitReader.java#L189-L225","documentation":"IncrementalSourceSplitReader throws this IOException from finishedSnapshotSplit when it is asked to finalize a snapshot split while its internal currentSplitId field is null, meaning no snapshot split is currently assigned to the reader. The reader tracks the active split id when a split is handed to it and uses it to report split completion; finalizing without an active split indicates the reader/split-enumerator state machine is out of sync, typically due to a checkpoint/restore or race in split assignment.","triggerScenarios":"fetch() detects the current fetcher has finished the snapshot split and calls finishedSnapshotSplit(), but currentSplitId is null because no split was assigned (or was already cleared after restore/failover) before completion was detected.","commonSituations":"Job recovery from a checkpoint where the split assignment was lost but the underlying Debezium fetcher still reports isFinished=true; a race where the enumerator reassigns splits while the old fetcher is finishing; bugs in custom split-assignment logic for CDC sources (MySQL/PostgreSQL CDC).","solutions":["Restart the job from the last consistent checkpoint so split assignment state and fetcher state are restored together.","Check that all source parallelism instances share a consistent checkpoint state (SnapshotSplitState / incremental source state serialization is intact).","Upgrade SeaTunnel to a version with fixes for CDC split-assignment races in IncrementalSourceSplitReader.","If reproducible, capture logs of emittedFinishedSplitId and currentFetcher and file an issue; the message includes both to diagnose which reader/fetcher desynced."],"exampleFix":"// defensive: skip finalization when no split is assigned\nif (currentSplitId == null) {\n    log.warn(\"Ignoring finished snapshot split signal with no assigned split\");\n    return;\n}\n// before (throws)\nthrow new IOException(String.format(\"Invalid state: currentSplitId is null ...\"));","handlingStrategy":"retry","validationCode":"// before relying on split completion, check assignment\nif (reader.getCurrentSplitId() == null) {\n    log.warn(\"No split assigned; request reassignment from enumerator\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    reader.fetch();\n} catch (IOException e) {\n    if (e.getMessage().contains(\"currentSplitId is null\")) {\n        // trigger checkpoint restore / reassignment instead of failing job\n    }\n}","preventionTips":["Always run CDC jobs with checkpointing enabled so split state is restored atomically.","Monitor for split-assignment gaps in logs after failover.","Keep SeaTunnel CDC connectors updated for split-state race fixes."],"tags":["cdc","source-reader","state-management","distributed-systems"],"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"}