apache/seatunnel · error · RuntimeException
Error sink previously failed for stage [%s], plugin [%s]
Error message
Error sink previously failed for stage [%s], plugin [%s]
What it means
DefaultErrorSinkWriter.writeAndCheckAccepted throws this when a previous failure of the error-sink worker thread has been recorded (workerFailure != null) and a new error row is submitted to a ROUTE-mode error sink. The writer is in a permanently failed state: every subsequent write rethrows the original worker failure wrapped in a new RuntimeException so the caller knows the error sink is dead and no rows can be routed there anymore.
Source
Thrown at seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/task/error/DefaultErrorSinkWriter.java:146
/** Initializes the child sink and worker thread during task startup. */
public void open() {
ensureInitialized();
}
@Override
public void write(RowErrorContext ctx, T row, Throwable t) throws Exception {
writeAndCheckAccepted(ctx, row, t);
}
@Override
public boolean writeAndCheckAccepted(RowErrorContext ctx, T row, Throwable t) throws Exception {
if (stageConfig.getMode() != ErrorHandlerMode.ROUTE) {
return false;
}
ensureInitialized();
if (workerFailure != null) {
throw new RuntimeException(
String.format(
"Error sink previously failed for stage [%s], plugin [%s]",
ctx.getStage(), ctx.getPluginName()),
workerFailure);
}
SeaTunnelRow errorRow = buildErrorRow(ctx, row, t);
try {
switch (stageConfig.getQueueOverflowPolicy()) {
case DROP:
pendingRows.incrementAndGet();
if (!queue.offer(errorRow)) {
pendingRows.decrementAndGet();
return false;
}
break;
case BLOCK:View on GitHub (pinned to cf67b549a7)
Solutions
- Inspect the wrapped cause (workerFailure) to find why the error sink worker originally failed
- Fix the underlying error sink problem (connectivity, schema, permissions) and restart/retry the job
- Enable checkpoint/restart so the task is fully reinitialized instead of writing into a failed writer
- If failure is transient, consider an ErrorHandlerMode other than ROUTE or add resilient error-sink config
Defensive patterns
Strategy: try-catch
Validate before calling
if (errorSinkWriter.isFailed()) { /* reroute or restart before writing */ } Try / catch
try {
writer.write(row);
} catch (RuntimeException e) {
if (e.getCause() instanceof Throwable && e.getMessage().contains("previously failed")) {
LOG.error("Error sink already dead; original cause: ", e.getCause());
throw e;
}
} Prevention
- Fix the first worker failure cause before retrying writes; the writer is permanently failed
- Monitor error-sink worker health and restart the task rather than reusing a failed writer
- Use resilient error sinks (local file/console) for ROUTE mode
- Enable job-level restart from checkpoint
When it happens
Trigger: The error-sink background worker thread threw an exception earlier (recorded in workerFailure), and afterwards write() -> writeAndCheckAccepted() is called again for stage ROUTE mode. The very next write after any worker crash hits this path.
Common situations: Error sink (e.g. configured console/jdbc error sink) crashed mid-job due to network outage or invalid credentials; job keeps producing error rows which now all fail with this wrapped exception; a deliberately failing test sink triggers it.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Error queue overflow for stage [%s], plugin [%s]
- Interrupted while enqueuing error row for error sink
- Error sink is closing for stage [%s], plugin [%s]
- COMMON-11
- Failed to invoke ${clazz.getName()}.${methodName}()
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/d6fa0664f15880a6.
Report an issue: GitHub.