{"record":{"id":"5b4dc62451a0d763","repo":"tailscale/tailscale","slug":"invalid-json-from-check-ip-forwarding-w","errorCode":null,"errorMessage":"invalid JSON from check-ip-forwarding: %w","messagePattern":"invalid JSON from check-ip-forwarding: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/local/local.go","lineNumber":898,"sourceCode":"// CheckIPForwarding asks the local Tailscale daemon whether it looks like the\n// machine is properly configured to forward IP packets as a subnet router\n// or exit node.\n//\n// API maturity: this method is not considered a stable API and is\n// subject to change between releases.\nfunc (lc *Client) CheckIPForwarding(ctx context.Context) error {\n\tif !buildfeatures.HasAdvertiseRoutes {\n\t\treturn nil\n\t}\n\tbody, err := lc.get200(ctx, \"/localapi/v0/check-ip-forwarding\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar jres struct {\n\t\tWarning string\n\t}\n\tif err := json.Unmarshal(body, &jres); err != nil {\n\t\treturn fmt.Errorf(\"invalid JSON from check-ip-forwarding: %w\", err)\n\t}\n\tif jres.Warning != \"\" {\n\t\treturn errors.New(jres.Warning)\n\t}\n\treturn nil\n}\n\n// CheckUDPGROForwarding asks the local Tailscale daemon whether it looks like\n// the machine is optimally configured to forward UDP packets as a subnet router\n// or exit node.\n//\n// API maturity: this method is not considered a stable API and is\n// subject to change between releases.\nfunc (lc *Client) CheckUDPGROForwarding(ctx context.Context) error {\n\tbody, err := lc.get200(ctx, \"/localapi/v0/check-udp-gro-forwarding\")\n\tif err != nil {\n\t\treturn err\n\t}","sourceCodeStart":880,"sourceCodeEnd":916,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/client/local/local.go#L880-L916","documentation":"Returned by Client.CheckIPForwarding (client/local/local.go:898) when the body from GET /localapi/v0/check-ip-forwarding — already a 200, since get200 succeeded — cannot be unmarshaled into the expected {Warning string} shape. The json error is wrapped, so offset/reason details survive. The endpoint's actual findings arrive as a Warning string returned as a separate error, not this one.","triggerScenarios":"A tailscaled version returning a different payload for check-ip-forwarding (schema drift); an empty or HTML body served with 200 by a proxy or wrapper transport in front of the local API; daemon internals erroring mid-response.","commonSituations":"Version-skewed subnet-router/exit-node check tooling; instrumented transports mangling the response; custom local API servers in tests returning wrong bodies.","solutions":["Fetch the raw body manually (curl --unix-socket .../check-ip-forwarding) and inspect it against the {\"Warning\": ...} shape","Align client and tailscaled versions","Remove any intermediary that rewrites local API responses","Treat the failure as 'check unavailable' rather than 'forwarding broken' in caller logic"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isCheckIPForwardingDecode(err error) bool {\n\tvar se *json.SyntaxError\n\treturn strings.Contains(err.Error(), \"invalid JSON from check-ip-forwarding\") && errors.As(err, &se)\n}","tryCatchPattern":"if err := lc.CheckIPForwarding(ctx); err != nil {\n\tif isCheckIPForwardingDecode(err) {\n\t\t// 200 but unparseable body: version skew or an intermediary — do not\n\t\t// report forwarding as broken\n\t\tlog.Printf(\"check-ip-forwarding unreadable: %v\", err)\n\t\treturn nil\n\t}\n\treturn err // real warning from the daemon\n}","preventionTips":["Do not equate 'check unavailable' with 'forwarding misconfigured' in caller logic","Keep client and daemon versions aligned when running subnet-router checks","Avoid wrapping local API responses in transforming proxies or test doubles that drop JSON"],"tags":["go","tailscale","local-api","json","subnet-router","exit-node"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}