vitessio/vitess · error · VitessError
VT13001
VT13001
Error message
VT13001: [BUG] should not see %T here
What it means
VT13001 internal-bug panic in planOffsets. Offsets are planned after horizons are merged; encountering a *Horizon operator during the offset-planning walk means a Horizon survived earlier phases when it should have been collapsed. It is an invariant violation indicating a planner bug, not user error.
Source
Thrown at go/vt/vtgate/planbuilder/operators/offset_planning.go:38
"fmt"
"vitess.io/vitess/go/vt/sqlparser"
"vitess.io/vitess/go/vt/vterrors"
"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
"vitess.io/vitess/go/vt/vtgate/semantics"
)
type offsettable interface {
Operator
planOffsets(ctx *plancontext.PlanningContext) Operator
}
// planOffsets will walk the tree top down, adding offset information to columns in the tree for use in further optimization,
func planOffsets(ctx *plancontext.PlanningContext, root Operator) Operator {
visitor := func(in Operator, _ semantics.TableSet, _ bool) (Operator, *ApplyResult) {
switch op := in.(type) {
case *Horizon:
panic(vterrors.VT13001(fmt.Sprintf("should not see %T here", in)))
case offsettable:
newOp := op.planOffsets(ctx)
if newOp == nil {
newOp = op
}
if DebugOperatorTree {
fmt.Println("Planned offsets for:")
fmt.Println(ToTree(newOp))
}
if newOp == op {
return newOp, nil
} else {
// We got a new operator from plan offsets. We should return that something has changed.
return newOp, Rewrote("planning offsets introduced a new operator")
}
}View on GitHub (pinned to 01a25a7d17)
Solutions
- Capture the failing SQL and stack trace and file a Vitess bug
- Rewrite the query (inline subqueries, simplify nested SELECTs) as a workaround
- Retry on the latest release where horizon-merging has been reworked
Example fix
// before SELECT * FROM (SELECT * FROM (SELECT id FROM t) a) b // after: flatten to a single-level query SELECT id FROM t
Defensive patterns
Strategy: fallback
Validate before calling
null
Type guard
null
Try / catch
try {
rows = await vtgate.execute(session, query, bindVars);
} catch (e) {
if (String(e.message).includes('VT13001')) {
log.error({query, err: e}, 'planner bug: horizon survived offset planning');
// retry with simplified/flattened query
}
throw e;
} Prevention
- Avoid deeply nested subquery layers
- Run planner-intensive queries through staging before upgrades
- Report VT13001 panics upstream with minimal repro
When it happens
Trigger: The offset-planning pass (planOffsets) visits a *Horizon node — i.e. a query whose Horizon was not merged/lowered before offset planning runs (subqueries/nested SELECTs hitting an unhandled path).
Common situations: Queries with nested subqueries or layered projections that keep a Horizon alive; regressions after planner upgrades.
Related errors
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/1ec765e795fea207.
Report an issue: GitHub.