redis/go-redis · error

invalid document ID format

Error message

invalid document ID format

What it means

In the FT.SEARCH (RESP2) reply, after the total, results come as alternating document-ID / fields pairs. The parser expects each ID to be a bulk string (Go string); a non-string element where an ID was expected aborts parsing with this error.

Source

Thrown at search_commands.go:2620

		val:     val,
	}
}

func parseFTSearch(data []interface{}, noContent, withScores, withPayloads, withSortKeys bool) (FTSearchResult, error) {
	if len(data) < 1 {
		return FTSearchResult{}, fmt.Errorf("unexpected search result format")
	}

	total, ok := data[0].(int64)
	if !ok {
		return FTSearchResult{}, fmt.Errorf("invalid total results format")
	}

	var results []Document
	for i := 1; i < len(data); {
		docID, ok := data[i].(string)
		if !ok {
			return FTSearchResult{}, fmt.Errorf("invalid document ID format")
		}

		doc := Document{
			ID:     docID,
			Fields: make(map[string]string),
		}
		i++

		if noContent {
			results = append(results, doc)
			continue
		}

		if withScores && i < len(data) {
			if scoreStr, ok := data[i].(string); ok {
				score, err := strconv.ParseFloat(scoreStr, 64)
				if err != nil {
					return FTSearchResult{}, fmt.Errorf("invalid score format")

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Ensure FTSearchOptions (NoContent, WithScores, WithPayloads, WithSortKeys) match the flags actually sent to the server
  2. Print the raw reply (RawResult) and check element ordering: total, id, [score], [payload], [sortkey], fields
  3. Fix any proxy or mock producing a shifted reply
  4. Upgrade go-redis and RediSearch to compatible versions

Example fix

// before: mock omits the fields array the flags expect
mock := []interface{}{int64(1), "doc:1"}

// after: include the fields array
cmd := rdb.FTSearch(ctx, "idx", "q")
mock := []interface{}{int64(1), "doc:1", []interface{}{"title", "hi"}}
Defensive patterns

Strategy: validation

Validate before calling

// Keep reply-shaping flags consistent between what you send and parse
opts := &redis.FTSearchOptions{NoContent: false, WithScores: true}
// mock layout must then be: [int64 total, id, score, fields, ...]

Type guard

func isStringAt(data []interface{}, i int) bool {
	return i < len(data)
	// caller: id, ok := data[i].(string)
}

Try / catch

res, err := rdb.FTSearch(ctx, "idx", q, opts).Result()
if err != nil {
	if strings.Contains(err.Error(), "invalid document ID") {
		log.Printf("reply misaligned with options %v: %v", opts, err)
	}
	return err
}

Prevention

When it happens

Trigger: FTSearch reply whose element at an odd offset is not a string — reply misalignment caused by withScores/withPayloads/withSortKeys options not matching the server's response, or a proxy/malformed mock producing the wrong layout.

Common situations: Client options (NoContent/WithScores/WithPayloads/WithSortKeys) differ from what the server was asked for, shifting element positions; intermediaries rewriting replies; bad mocks.

Related errors


AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01). Data as JSON: /api/errors/44291937dd1c16d0. Report an issue: GitHub.