Tencent/WeKnora · error · AppError

opensearch: cluster version unsupported

Error message

opensearch: cluster version unsupported

What it means

An AlternativeSubPlan node (planner construct for choosing between IN-list subplans) was found. Like SubPlan, it is an internal representation that never comes from raw user SQL parsing; its presence indicates forged or non-parse-tree input, and the validator blocks it defensively.

Source

Thrown at internal/application/repository/retriever/opensearch/errors.go:33

	ErrIndexNotFound = errors.New("opensearch: index not found")

	// ErrDimensionMismatch — embedding dimension violates the per-dim
	// invariant (e.g. dim <= 0, dim > 16000, or embeddings within a
	// single batch disagree).
	ErrDimensionMismatch = errors.New("opensearch: embedding dimension mismatch")

	// ErrAuth — cluster returned 401 / 403. Distinguished from ErrTransport
	// so the service layer can map to a clean 4xx instead of 503.
	ErrAuth = errors.New("opensearch: authentication failed")

	// ErrTransport — network / 5xx / opaque cluster error. Classified as
	// transient: ensureReady does NOT persist this in initErr, so the next
	// caller will retry.
	ErrTransport = errors.New("opensearch: transport error")

	// ErrVersionUnsupported — cluster is not OpenSearch, is OS 1.x, or is
	// OS 2.0~2.3 (pre-Lucene-HNSW-GA). probeVersion enforces.
	ErrVersionUnsupported = errors.New("opensearch: cluster version unsupported")

	// ErrConfigInvalid — IndexConfig / storeID / sanitizeIndexName guard
	// failed, or the k-NN plugin is missing on one or more cluster nodes.
	ErrConfigInvalid = errors.New("opensearch: invalid index config")

	// ErrFeatureNotEnabled — stubs.go returns this from methods whose real
	// implementation has not landed yet (CopyIndices / BatchUpdateChunk* /
	// swapToVersion, plus the read/write methods that a follow-up commit
	// will replace with production code).
	ErrFeatureNotEnabled = errors.New("opensearch: feature not enabled in this build")

	// ErrBatchTooLarge — Save / Delete batch exceeded the driver's sync
	// cap. Distinct from ErrFeatureNotEnabled so the service layer can
	// chunk + retry rather than treat the failure as "waiting on a future
	// implementation."
	ErrBatchTooLarge = errors.New("opensearch: batch size exceeds driver cap")

	// ErrCircuitBreaker — OpenSearch k-NN circuit breaker returned 429

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Only feed trees obtained from parsing raw SQL text via pg_query.Parse into the validator.
  2. Remove any step that mutates or regenerates the AST between parse and validation.
  3. Validate SQL text at the API boundary and keep parse->validate as a single synchronous step.

Example fix

// before
node := loadCachedTree(key) // may be a planner tree
v.validateNode(node, res)

// after
tree, _ := pg_query.Parse(rawSQL)
v.validateNode(tree.Stmts[0].Stmt, res)
Defensive patterns

Strategy: type-guard

Validate before calling

func validateRawSQL(rawSQL string) error {
	tree, err := pg_query.Parse(rawSQL)
	if err != nil { return err }
	return validator.ValidateNodeTree(tree) // trees only from fresh Parse
}

Type guard

if _, ok := node.Node.(*pg_query.Node_AlternativeSubPlan); ok {
	return false // planner-only node: input is not a raw parse tree
}

Try / catch

if err := validator.ValidateQuery(rawSQL); err != nil {
	if strings.Contains(err.Error(), "AlternativeSubPlan") {
		return fmt.Errorf("reject non-parse-tree input: %w", err)
	}
}

Prevention

When it happens

Trigger: Validating a node tree that was produced or mutated after planning, or hand-crafted protobuf ASTs containing Node_AlternativeSubPlan.

Common situations: Pipelines that persist/restore planner trees; fuzzing or adversarial input aimed at the validator; mixing plan output into the parse-validate path.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/bf2c2227f8c1fbaa. Report an issue: GitHub.