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

  1. Inspect the wrapped cause (workerFailure) to find why the error sink worker originally failed
  2. Fix the underlying error sink problem (connectivity, schema, permissions) and restart/retry the job
  3. Enable checkpoint/restart so the task is fully reinitialized instead of writing into a failed writer
  4. 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

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


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/d6fa0664f15880a6. Report an issue: GitHub.