{"record":{"id":"8b016d4f7010e850","repo":"XTLS/Xray-core","slug":"addrule-config-type-error","errorCode":null,"errorMessage":"AddRule: config type error","messagePattern":"AddRule: config type error","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/router/router.go","lineNumber":120,"sourceCode":"\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif rule.Webhook != nil {\n\t\trule.Webhook.Fire(originalCtx, tag)\n\t}\n\treturn &Route{Context: ctx, outboundTag: tag, ruleTag: rule.RuleTag}, nil\n}\n\n// AddRule implements routing.Router.\nfunc (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {\n\tinst, err := config.GetInstance()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c, ok := inst.(*Config); ok {\n\t\treturn r.ReloadRules(c, shouldAppend)\n\t}\n\treturn errors.New(\"AddRule: config type error\")\n}\n\nfunc (r *Router) ReloadRules(config *Config, shouldAppend bool) error {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif !shouldAppend {\n\t\tfor _, rule := range r.rules {\n\t\t\tif rule.Webhook != nil {\n\t\t\t\trule.Webhook.Close()\n\t\t\t}\n\t\t}\n\t\tr.balancers = make(map[string]*Balancer, len(config.BalancingRule))\n\t\tr.rules = make([]*Rule, 0, len(config.Rule))\n\t}\n\tfor _, rule := range config.BalancingRule {\n\t\t_, found := r.balancers[rule.Tag]\n\t\tif found {","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/router/router.go#L102-L138","documentation":"AddRule expects the TypedMessage payload to decode to the router's *Config type (a full routing config). Any other message type is rejected because rule addition is implemented as a partial config reload.","triggerScenarios":"Calling AddRule with a serial.TypedMessage whose @type resolves to something other than app.router.Config (e.g. a raw rule message or a config from a different module).","commonSituations":"Clients assuming AddRule takes a single rule object; cross-version protobuf type URLs; hand-built TypedMessage with wrong type URL.","solutions":["Wrap the rule inside a routing Config message: TypedMessage of app.router.Config containing the new rule","Use a helper (serial.ToTypedMessage(&router.Config{Rule: [...]})) to build the payload","Verify the @type string matches the registered Config message"],"exampleFix":"// before\nmsg := serial.ToTypedMessage(&router.Rule{}) // wrong type\nrouter.AddRule(msg, true) // config type error\n\n// after\nmsg := serial.ToTypedMessage(&router.Config{Rule: []*router.RoutingRule{rule}})\nrouter.AddRule(msg, true)","handlingStrategy":"type-guard","validationCode":"inst, err := msg.GetInstance()\nif err != nil { return err }\nif _, ok := inst.(*router.Config); !ok {\n    return fmt.Errorf(\"AddRule expects app.router.Config, got %T\", inst)\n}","typeGuard":"func isRouterConfig(msg *serial.TypedMessage) bool {\n    i, err := msg.GetInstance()\n    return err == nil && isConfigType(i)\n}\nfunc isConfigType(i interface{}) bool { _, ok := i.(*router.Config); return ok }","tryCatchPattern":null,"preventionTips":["Build AddRule payloads with serial.ToTypedMessage(&router.Config{...})","Encapsulate gRPC call construction in one client module","Unit-test payload types against the server assertion"],"tags":["routing","grpc","api","type-mismatch"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}