{"record":{"id":"aa131cfeb1fb584b","repo":"nats-io/nats-server","slug":"v","errorCode":null,"errorMessage":"%v","messagePattern":"%v","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/opts.go","lineNumber":1023,"sourceCode":"\t\treturn\n\t} else if lastToken != nil && *lastToken != nil {\n\t\t*errors = append(*errors, &configErr{*lastToken, fmt.Sprint(err)})\n\t} else {\n\t\t*errors = append(*errors, fmt.Errorf(\"encountered panic without a token %v\", err))\n\t}\n}\n\n// use in defer to recover from panic and turn it into an error associated with last token\nfunc convertPanicToError(lastToken *token, e *error) {\n\t// only recover if an error can be stored\n\tif e == nil || *e != nil {\n\t\treturn\n\t} else if err := recover(); err == nil {\n\t\treturn\n\t} else if lastToken != nil && *lastToken != nil {\n\t\t*e = &configErr{*lastToken, fmt.Sprint(err)}\n\t} else {\n\t\t*e = fmt.Errorf(\"%v\", err)\n\t}\n}\n\n// configureSystemAccount configures a system account\n// if present in the configuration.\nfunc configureSystemAccount(o *Options, m map[string]any) (retErr error) {\n\tvar lt token\n\tdefer convertPanicToError(&lt, &retErr)\n\tconfigure := func(v any) error {\n\t\ttk, v := unwrapValue(v, &lt)\n\t\tsa, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn &configErr{tk, \"system account name must be a string\"}\n\t\t}\n\t\to.SystemAccount = sa\n\t\treturn nil\n\t}\n","sourceCodeStart":1005,"sourceCodeEnd":1041,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/opts.go#L1005-L1041","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the panic text (e.g. 'interface conversion: interface {} is string, not map[string]interface {}') and fix the value type at that config key.","Ensure each option block uses the documented structure (maps for blocks, arrays for routes/clusters).","Run the server with a minimal config and add sections back until the panic reappears.","Check the config against the current version's sample config — option shapes change between releases."],"exampleFix":"// before\nroutes: nats-route://host:4222\n// after\nroutes: [\n\tnats-route://host:4222\n]","handlingStrategy":"validation","validationCode":"// ensure block options are maps before running the server\nfor _, block := range []string{\"cluster\", \"gateway\", \"leafnodes\", \"monitor\"} {\n\tif v, ok := cfg[block]; ok {\n\t\tif _, isMap := v.(map[string]any); !isMap && v != nil {\n\t\t\tfmt.Printf(\"%s must be a block, got %T\\n\", block, v)\n\t\t}\n\t}\n}","typeGuard":"func isBlock(v any) bool {\n\t_, ok := v.(map[string]any)\n\treturn ok\n}","tryCatchPattern":null,"preventionTips":["Quote values that must remain strings in YAML.","Compare configs against the sample config for the running version.","Diff config changes and review scalar-vs-block mistakes."],"tags":["config","panic","type-mismatch"],"backgroundTag":"config-value-wrong-type","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}