{"record":{"id":"95a3501ddea30066","repo":"vitessio/vitess","slug":"must-specify-at-least-one-keyspace-shard-to-delete","errorCode":null,"errorMessage":"must specify at least one keyspace_shard to delete (got %+v)","messagePattern":"must specify at least one keyspace_shard to delete \\(got %\\+v\\)","errorType":"http","errorClass":"errors.BadRequest","httpStatus":400,"severity":"error","filePath":"go/vt/vtadmin/http/shards.go","lineNumber":92,"sourceCode":"\tshards := make([]*vtctldatapb.Shard, len(shardList))\n\tfor i, kss := range shardList {\n\t\tks, shard, err := topoproto.ParseKeyspaceShard(kss)\n\t\tif err != nil {\n\t\t\treturn NewJSONResponse(nil, &errors.BadRequest{\n\t\t\t\tErr: fmt.Errorf(\"%w: parsing %s at position %d\", err, kss, i),\n\t\t\t})\n\t\t}\n\n\t\tshards[i] = &vtctldatapb.Shard{\n\t\t\tKeyspace: ks,\n\t\t\tName:     shard,\n\t\t\tShard:    &topodatapb.Shard{},\n\t\t}\n\t}\n\n\tif len(shards) == 0 {\n\t\treturn NewJSONResponse(nil, &errors.BadRequest{\n\t\t\tErr: fmt.Errorf(\"must specify at least one keyspace_shard to delete (got %+v)\", shardList),\n\t\t})\n\t}\n\n\tresp, err := api.server.DeleteShards(ctx, &vtadminpb.DeleteShardsRequest{\n\t\tClusterId: vars[\"cluster_id\"],\n\t\tOptions: &vtctldatapb.DeleteShardsRequest{\n\t\t\tShards:        shards,\n\t\t\tRecursive:     recursive,\n\t\t\tEvenIfServing: evenIfServing,\n\t\t},\n\t})\n\treturn NewJSONResponse(resp, err)\n}\n\n// EmergencyFailoverShard implements the http wrapper for\n// POST /shard/{cluster_id}/{keyspace}/{shard}/emergency_failover.\n//\n// Query params: none","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtadmin/http/shards.go#L74-L110","documentation":"DeleteShards requires at least one shard to operate on; if the parsed shard list is empty, the handler returns a 400 BadRequest telling the caller to specify at least one keyspace_shard. This guards the vtctld DeleteShards RPC, which would otherwise fail downstream, by rejecting the request early with a clear message.","triggerScenarios":"Calling the delete-shards endpoint without any `keyspace_shard` query parameters, or with only empty values such that the parsed shard list is zero-length.","commonSituations":"Client omits the query parameter entirely; UI sends an empty selection; list built from a split of an empty string producing an empty/blank list.","solutions":["Include at least one valid keyspace_shard query parameter, e.g. ?keyspace_shard=commerce/0.","Client-side: check the selected shard list is non-empty before issuing the request.","Filter out empty strings before sending so blank entries don't produce a request with no real targets."],"exampleFix":"// before\nconst qs = shards.map(s => `keyspace_shard=${s}`).join('&')\n// after\nif (shards.length === 0) throw new Error('select at least one shard')\nconst qs = shards.map(s => `keyspace_shard=${encodeURIComponent(s)}`).join('&')","handlingStrategy":"validation","validationCode":"shards := nonEmpty(keyspaceShards)\nif len(shards) == 0 {\n\treturn errors.New(\"must provide at least one keyspace_shard\")\n}","typeGuard":null,"tryCatchPattern":"resp, err := http.Post(u, \"application/json\", body)\nif err == nil && resp.StatusCode == 400 {\n\tvar e vtadminErr\n\t_ = json.NewDecoder(resp.Body).Decode(&e)\n\treturn fmt.Errorf(\"request invalid: %v\", e)\n}","preventionTips":["Require shard selection in the UI/API client before enabling the delete action.","Filter empty strings before serializing query parameters.","Add a client-side assertion that len(shards) > 0 before sending."],"tags":["http","bad-request","validation","vtadmin"],"backgroundTag":"missing-required-argument","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}