vitessio/vitess · error · errors.BadRequest

%w: parsing %s at position %d

Error message

%w: parsing %s at position %d

What it means

The DeleteShards HTTP handler accepts `keyspace_shard` query parameters as "keyspace/shard" strings and parses each with ParseKeyspaceShard. When any entry is malformed, it returns a 400 BadRequest wrapping the parse error plus the offending string and its index in the list. It exists to give the client a precise indication of which list element failed.

Source

Thrown at go/vt/vtadmin/http/shards.go:79

	vars := mux.Vars(r.Request)
	recursive, err := r.ParseQueryParamAsBool("recursive", false)
	if err != nil {
		return NewJSONResponse(nil, err)
	}

	evenIfServing, err := r.ParseQueryParamAsBool("even_if_serving", false)
	if err != nil {
		return NewJSONResponse(nil, err)
	}

	shardList := r.URL.Query()["keyspace_shard"]
	shardList = sets.List(sets.New(shardList...))
	shards := make([]*vtctldatapb.Shard, len(shardList))
	for i, kss := range shardList {
		ks, shard, err := topoproto.ParseKeyspaceShard(kss)
		if err != nil {
			return NewJSONResponse(nil, &errors.BadRequest{
				Err: fmt.Errorf("%w: parsing %s at position %d", err, kss, i),
			})
		}

		shards[i] = &vtctldatapb.Shard{
			Keyspace: ks,
			Name:     shard,
			Shard:    &topodatapb.Shard{},
		}
	}

	if len(shards) == 0 {
		return NewJSONResponse(nil, &errors.BadRequest{
			Err: fmt.Errorf("must specify at least one keyspace_shard to delete (got %+v)", shardList),
		})
	}

	resp, err := api.server.DeleteShards(ctx, &vtadminpb.DeleteShardsRequest{
		ClusterId: vars["cluster_id"],

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Fix the client to send valid keyspace/shard strings like "commerce/0".
  2. Validate each entry with topoproto.ParseKeyspaceShard on the client side before calling the API.
  3. Trim/split query lists carefully to avoid empty segments reaching the endpoint.
  4. Read the error's 'at position %d' field to find which list element to correct.

Example fix

// before
fetch(`/api/shards/delete?keyspace_shard=${ks}`) // ks = "commerce-0"
// after
const ksShard = `${keyspace}/${shard}` // "commerce/0"
fetch(`/api/shards/delete?keyspace_shard=${encodeURIComponent(ksShard)}`)
Defensive patterns

Strategy: validation

Validate before calling

for i, ksShard := range keyspaceShards {
	if _, _, err := topoproto.ParseKeyspaceShard(ksShard); err != nil {
		return fmt.Errorf("invalid keyspace_shard %q at index %d", ksShard, i)
	}
}

Type guard

func isKeyspaceShard(s string) bool {
	parts := strings.Split(s, "/")
	return len(parts) == 2 && parts[0] != "" && parts[1] != ""
}

Try / catch

resp, err := http.Get(u)
if err != nil {
	return err
}
if resp.StatusCode == http.StatusBadRequest {
	var e vtadminErr
	_ = json.NewDecoder(resp.Body).Decode(&e)
	return fmt.Errorf("delete-shards rejected: %v", e)
}

Prevention

When it happens

Trigger: GET/POST to the delete-shards endpoint with a keyspace_shard value that has no '/' separator, is empty, has too many parts (a/b/c), or is otherwise rejected by topoproto.ParseKeyspaceShard.

Common situations: API clients URL-encoding or quoting incorrectly; passing tablet-style keys ("keyspace shard" or fully qualified tablet paths) instead of keyspace/shard; empty strings in a comma-split list.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/5dc867a8345238db. Report an issue: GitHub.