vitessio/vitess · error

failed to unmarshal staticfile config from json: %w

Error message

failed to unmarshal staticfile config from json: %w

What it means

JSONDiscovery.parseConfig loads the vtadmin cluster's static/dynamic JSON config file into its in-memory config struct. If json.Unmarshal fails (malformed JSON or a type mismatch, e.g. a string where an array is expected), the error is wrapped and returned. This runs both for NewStaticFile at startup and for NewDynamic file-watch reloads.

Source

Thrown at go/vt/vtadmin/cluster/discovery/discovery_json.go:84

	Vtctlds []*JSONVtctldConfig `json:"vtctlds,omitempty"`
}

// JSONVTGateConfig contains host and tag information for a single VTGate in a cluster.
type JSONVTGateConfig struct {
	Host *vtadminpb.VTGate `json:"host"`
	Tags []string          `json:"tags"`
}

// JSONVtctldConfig contains a host and tag information for a single
// Vtctld in a cluster.
type JSONVtctldConfig struct {
	Host *vtadminpb.Vtctld `json:"host"`
	Tags []string          `json:"tags"`
}

func (d *JSONDiscovery) parseConfig(bytes []byte) error {
	if err := json.Unmarshal(bytes, &d.config); err != nil {
		return fmt.Errorf("failed to unmarshal staticfile config from json: %w", err)
	}

	d.gates.byName = make(map[string]*vtadminpb.VTGate, len(d.config.VTGates))
	d.gates.byTag = make(map[string][]*vtadminpb.VTGate)

	// Index the gates by name and by tag for easier lookups
	for _, gate := range d.config.VTGates {
		d.gates.byName[gate.Host.Hostname] = gate.Host

		for _, tag := range gate.Tags {
			d.gates.byTag[tag] = append(d.gates.byTag[tag], gate.Host)
		}
	}

	d.vtctlds.byName = make(map[string]*vtadminpb.Vtctld, len(d.config.Vtctlds))
	d.vtctlds.byTag = make(map[string][]*vtadminpb.Vtctld)

	// Index the vtctlds by name and by tag for easier lookups

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Validate the JSON file with a linter (jq, jsonlint) and fix the syntax/type error reported there
  2. Cross-check each field's type against the discovery JSON schema (host must be a vtctld object, tags an array of strings)
  3. Restore the file from a known-good backup or regenerate it

Example fix

// before (broken)
{"vtctlds": {"host": "vtctld1", "tags": "primary"}}
// after
{"vtctlds": {"host": "vtctld1", "tags": ["primary"]}}
Defensive patterns

Strategy: validation

Validate before calling

var probe map[string]any
if err := json.Unmarshal(bytes, &probe); err != nil {
	return fmt.Errorf("discovery config is not valid JSON: %w", err)
}
// optionally json.Valid(bytes) as a cheap pre-check
if !json.Valid(bytes) {
	return errors.New("discovery config file contains invalid JSON")
}

Type guard

func isValidDiscoveryConfig(b []byte) bool {
	var d discoveryConfig
	return json.Unmarshal(b, &d) == nil && d.VTGates != nil
}

Try / catch

if err := json.Unmarshal(data, &cfg); err != nil {
	var typeErr *json.UnmarshalTypeError
	if errors.As(err, &typeErr) {
		log.Errorf("type mismatch at %s (want %s): %v", typeErr.Field, typeErr.Type, typeErr)
	}
	return err
}

Prevention

When it happens

Trigger: Calling cluster.NewStaticFile or NewDynamic with a JSON file that is syntactically invalid, uses the wrong types for known fields (host, tags, vtgates), or has unexpected values like a number for a string field.

Common situations: Hand-edited discovery JSON with a trailing comma or missing quote; automated tooling wrote YAML into a .json file; field type changed (tags given as a string instead of an array); truncated file from a failed write.

Related errors


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