nats-io/nats-server · error

%v

Error message

%v

What it means

convertPanicToError recovers from a panic during configuration processing and converts it to an error: if a config token is known it becomes a configErr tied to that option, otherwise it becomes fmt.Errorf("%v", err) — this bare message. Developers see the raw panic value (e.g. a runtime type-assertion message) with no wrapper text, attributed to no specific option when no token exists.

Source

Thrown at server/opts.go:1023

		return
	} else if lastToken != nil && *lastToken != nil {
		*errors = append(*errors, &configErr{*lastToken, fmt.Sprint(err)})
	} else {
		*errors = append(*errors, fmt.Errorf("encountered panic without a token %v", err))
	}
}

// use in defer to recover from panic and turn it into an error associated with last token
func convertPanicToError(lastToken *token, e *error) {
	// only recover if an error can be stored
	if e == nil || *e != nil {
		return
	} else if err := recover(); err == nil {
		return
	} else if lastToken != nil && *lastToken != nil {
		*e = &configErr{*lastToken, fmt.Sprint(err)}
	} else {
		*e = fmt.Errorf("%v", err)
	}
}

// configureSystemAccount configures a system account
// if present in the configuration.
func configureSystemAccount(o *Options, m map[string]any) (retErr error) {
	var lt token
	defer convertPanicToError(&lt, &retErr)
	configure := func(v any) error {
		tk, v := unwrapValue(v, &lt)
		sa, ok := v.(string)
		if !ok {
			return &configErr{tk, "system account name must be a string"}
		}
		o.SystemAccount = sa
		return nil
	}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Read the panic text (e.g. 'interface conversion: interface {} is string, not map[string]interface {}') and fix the value type at that config key.
  2. Ensure each option block uses the documented structure (maps for blocks, arrays for routes/clusters).
  3. Run the server with a minimal config and add sections back until the panic reappears.
  4. Check the config against the current version's sample config — option shapes change between releases.

Example fix

// before
routes: nats-route://host:4222
// after
routes: [
	nats-route://host:4222
]
Defensive patterns

Strategy: validation

Validate before calling

// ensure block options are maps before running the server
for _, block := range []string{"cluster", "gateway", "leafnodes", "monitor"} {
	if v, ok := cfg[block]; ok {
		if _, isMap := v.(map[string]any); !isMap && v != nil {
			fmt.Printf("%s must be a block, got %T\n", block, v)
		}
	}
}

Type guard

func isBlock(v any) bool {
	_, ok := v.(map[string]any)
	return ok
}

Prevention

When it happens

Trigger: A panic inside any config parsing helper deferred with convertPanicToError where lastToken is nil, e.g. a failed type assertion like v.(map[string]any) on a scalar config value.

Common situations: YAML/JSON config where a block (host:port map, routes list) is given as a scalar or wrong type; empty or null sections; wrong nesting levels in the config file.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/aa131cfeb1fb584b. Report an issue: GitHub.