risingwavelabs/risingwave · error

invalid MATCH_RECOGNIZE after_match_skip mode: {}

Error message

invalid MATCH_RECOGNIZE after_match_skip mode: {}

What it means

The proto enum `after_match_skip.mode` is `Unspecified`, meaning the producer never set a valid skip mode. The decoder maps PastLastRow/ToNextRow/ToFirst/ToLast to executor `SkipMode`s and treats Unspecified as invalid input. Proto3 leaves scalar/enum defaults at 0, so this usually indicates an unset field.

Source

Thrown at src/stream/src/from_proto/match_recognize.rs:132

        // would mask a corrupt plan or a version skew.
        let skip = {
            use risingwave_pb::stream_plan::match_recognize_after_match_skip::Mode;
            let pb_skip = node
                .after_match_skip
                .as_ref()
                .ok_or_else(|| anyhow::anyhow!("MATCH_RECOGNIZE node missing after_match_skip"))?;
            let target = || {
                pb_skip.target.clone().ok_or_else(|| {
                    anyhow::anyhow!("AFTER MATCH SKIP TO FIRST/LAST missing its target variable")
                })
            };
            match pb_skip.mode() {
                Mode::PastLastRow => SkipMode::PastLastRow,
                Mode::ToNextRow => SkipMode::ToNextRow,
                Mode::ToFirst => SkipMode::ToFirst(target()?),
                Mode::ToLast => SkipMode::ToLast(target()?),
                Mode::Unspecified => {
                    return Err(anyhow::anyhow!(
                        "invalid MATCH_RECOGNIZE after_match_skip mode: {}",
                        pb_skip.mode
                    )
                    .into());
                }
            }
        };

        let within = node
            .within
            .as_ref()
            .map(|e| build_non_strict_from_prost(e, params.eval_error_report.clone()))
            .transpose()?;
        // Over `DeadlineErrorReport`, not the actor's report directly: `first + bound` leaving the
        // order key's range is the window that never closes, not a compute error to count and log
        // per row. See `eval_deadline` in the executor.
        let within_deadline = node
            .within_deadline

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Set an explicit mode on the AfterMatchSkip proto (e.g. `mode: PAST_LAST_ROW`).
  2. If writing SQL, add an `AFTER MATCH SKIP ...` clause so the binder emits a concrete mode.
  3. Re-create the streaming job to regenerate the plan with a current binder.
  4. Check for version skew between producer and consumer nodes.

Example fix

// before
AfterMatchSkip { mode: Mode::Unspecified as i32, target: None }
// after
AfterMatchSkip { mode: Mode::PastLastRow as i32, target: None }
Defensive patterns

Strategy: validation

Validate before calling

// Check the mode is a known value before decoding
fn skip_mode_valid(skip: &AfterMatchSkip) -> bool {
    use risingwave_pb::stream_plan::match_recognize_after_match_skip::Mode;
    !matches!(skip.mode(), Mode::Unspecified)
}

Try / catch

// Wrap decode errors with the offending raw value
let mode = Mode::try_from(pb_skip.mode)
    .map_err(|e| anyhow!("unknown after_match_skip mode {}: {e}", pb_skip.mode))?;

Prevention

When it happens

Trigger: `new_boxed_executor` receives an `after_match_skip` whose `mode` is `MODE_UNSPECIFIED` (0) — typically from a hand-built proto, an older producer that set the message but not the mode, or corrupted serialization.

Common situations: Version skew after adding the skip enum; test fixtures constructing AfterMatchSkip without setting `mode`; plans from a foreign/hand-crafted producer.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/51a572ecb2a99723. Report an issue: GitHub.