#1598·ginkgo

Ginkgo Reporter slice-bounds panic — timeline offsets can go backward

Author: slfletchCreated Sep 30, 2025Updated Mar 21, 2026

DefaultReporter.emitTimeline can panic with slice bounds out of range when timeline entries arrive out of order relative to CapturedGinkgoWriterOutput (gw). The code slices using gw[cursor:tl.Offset] after only checking tl.Offset < len(gw). If tl.Offset < cursor, the slice start > end triggers a panic.

Actual behavior Occasional panic in reporter:

panic: runtime error: slice bounds out of range [start:cursor] with capacity len at ginkgo/v2/reporters/default_reporter.go:emitTimeline r.emit(r.fi(indent, "%s", gw[cursor:tl.Offset]))

Expected behavior Reporter should never panic; it should tolerate out-of-order or beyond-end offsets and still emit the buffered writer output.

Root cause

emitTimeline assumes cursor ≤ tl.Offset ≤ len(gw). With concurrency, timeline entries can be recorded/emitted out of order, so tl.Offset can be less than cursor, violating the assumption.

Minimal fix:

func (r *DefaultReporter) emitTimeline(indent uint, report types.SpecReport, timeline types.Timeline) { isVeryVerbose := r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose) gw := report.CapturedGinkgoWriterOutput cursor := 0 for _, entry := range timeline { tl := entry.GetTimelineLocation()

    end := tl.Offset
    if end > len(gw) { end = len(gw) }
    if end < cursor  { end = cursor  }
    if cursor < end {
        r.emit(r.fi(indent, "%s", gw[cursor:end]))
        cursor = end
    } else if cursor < len(gw) && end == len(gw) {
        r.emit(r.fi(indent, "%s", gw[cursor:]))
        cursor = len(gw)
    }

    switch x := entry.(type) {
    case types.Failure:
        if isVeryVerbose { r.emitFailure(indent, report.State, x, false) } else { r.emitShortFailure(indent, report.State, x) }
    case types.AdditionalFailure:
        if isVeryVerbose { r.emitFailure(indent, x.State, x.Failure, true) } else { r.emitShortFailure(indent, x.State, x.Failure) }
    case types.ReportEntry:
        r.emitReportEntry(indent, x)
    case types.ProgressReport:
        r.emitProgressReport(indent, false, x)
    case types.SpecEvent:
        if isVeryVerbose || !x.IsOnlyVisibleAtVeryVerbose() || r.conf.ShowNodeEvents {
            r.emitSpecEvent(indent, x, isVeryVerbose)
        }
    }
}
if cursor < len(gw) {
    r.emit(r.fi(indent, "%s", gw[cursor:]))
}

}

Reproduceable steps:

Create multiple goroutines that (a) write to GinkgoWriter, (b) emit timeline entries, and (c) complete in an order different from creation—this can yield tl.Offset < cursor for a later-emitted entry.