apache/beam · critical
error decoding watermarks
Error message
error decoding watermarks
What it means
Prism's element manager decodes a Beam windowed-value header from a bundle's teed data stream via DecodeWindowedValueHeader. This panic fires when the header bytes cannot be decoded into windows/key/partition and the error is not a clean io.EOF. It indicates corrupt, truncated, or mis-coded data between the SDK harness and the runner.
Solutions
- Verify the window and value coders declared by the pipeline match the SDK harness's actual encoding; force length-prefixed coders if using custom window coders
- Ensure the SDK harness container version matches the prism runner version (version skew causes coder mismatches)
- Log and dump the failing bundle's raw bytes to identify which PCollection has the corrupt stream
- Update to the latest Apache Beam release; several windowed-header decoding bugs have been fixed
- File an issue with the pipeline and coder definitions if the bytes look correct
Example fix
// before: panic on any non-EOF decode error
if err != nil {
if err == io.EOF { break }
panic("error decoding watermarks")
}
// after: capture more diagnostics before failing
ws, et, pn, err := exec.DecodeWindowedValueHeader(info.WDec, tee)
if err != nil {
if err == io.EOF { break }
slog.Error("PersistBundle: error decoding watermarks", "error", err, "bundle", rb, "output", output)
return fmt.Errorf("decoding windowed value header for bundle %v output %v: %w", rb, output, err)
} Defensive patterns
Strategy: validation
Validate before calling
// Before running: verify coder configuration
if !strings.Contains(coder.Ref, "windowed") || windowCoder == nil {
return fmt.Errorf("PCollection %s missing window coder", pcolID)
} Prevention
- Pin identical Beam versions for SDK harness images and the prism runner
- Prefer standard windowing strategies and coders over custom ones
- Add integration smoke tests exercising windowed pipelines before upgrades
When it happens
Trigger: DecodeWindowedValueHeader returns a non-EOF error while persisting a bundle — typically a coder mismatch between what the SDK declared and the bytes it sent, or a truncated stream. The code breaks cleanly on io.EOF but panics on any other decode error.
Common situations: Custom or misconfigured coders (e.g. a window coder not length-prefixed), Beam SDK/runner version skew between harness container and prism, corrupted data channels during harness instability.
Understand the failure class
Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.
Related errors
- couldn't decode characteristic for variant
- error decoding append bag user state window key
- error decoding residual header:
- error re-encoding characteristic for variant
- generating bundle for stage
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/167b1ceee795c5fe.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go:899
for output, data := range d.Raw {
info := col2Coders[output]
var newPending []element
slog.Debug("PersistBundle: processing output", "bundle", rb, slog.String("output", output))
for _, datum := range data {
buf := bytes.NewBuffer(datum)
if len(datum) == 0 {
panic(fmt.Sprintf("zero length data for %v: ", output))
}
for {
var rawBytes bytes.Buffer
tee := io.TeeReader(buf, &rawBytes)
ws, et, pn, err := exec.DecodeWindowedValueHeader(info.WDec, tee)
if err != nil {
if err == io.EOF {
break
}
slog.Error("PersistBundle: error decoding watermarks", "error", err, "bundle", rb, slog.String("output", output))
panic("error decoding watermarks")
}
if len(ws) == 0 {
slog.Warn("PersistBundle: sdk provided a windowed value header 0 windows", "bundle", rb)
}
// TODO: Optimize unnecessary copies. This is doubleteeing.
elmBytes := info.EDec(tee)
var keyBytes []byte
if info.KeyDec != nil {
kbuf := bytes.NewBuffer(elmBytes)
keyBytes = info.KeyDec(kbuf) // TODO: Optimize unnecessary copies. This is tripleteeing?
}
for _, w := range ws {
newPending = append(newPending,
element{
window: w,
timestamp: et,
pane: stage.kind.getPaneOrDefault(stage, pn, w, keyBytes, rb.BundleID),
elmBytes: elmBytes,View on GitHub (pinned to 12126d8942)