{"record":{"id":"cf38daaa6e0b8a34","repo":"k3s-io/k3s","slug":"failed-to-unmarshal-tailscale-output-v","errorCode":null,"errorMessage":"failed to unmarshal tailscale output: %v","messagePattern":"failed to unmarshal tailscale output: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/vpn/vpn.go","lineNumber":171,"sourceCode":"\t\treturn nil\n\t}\n\n\treturn errors.New(\"Requested VPN: \" + authInfo.Name + \" is not supported. We currently only support tailscale\")\n}\n\n// getTailscaleInfo returns the IPs of the interface\nfunc getTailscaleInfo() (*Info, error) {\n\toutput, err := util.ExecCommand(\"tailscale\", []string{\"status\", \"--json\"})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to run tailscale status --json: %v\", err)\n\t}\n\n\tlogrus.Debugf(\"Output from tailscale status --json: %v\", output)\n\n\tvar tailscaleOutput TailscaleOutput\n\terr = json.Unmarshal([]byte(output), &tailscaleOutput)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal tailscale output: %v\", err)\n\t}\n\n\t// Errors are ignored because the interface might not have ipv4 or ipv6 addresses (that's the only possible error)\n\tipv4Address, _ := util.GetFirst4String(tailscaleOutput.TailscaleIPs)\n\tipv6Address, _ := util.GetFirst6String(tailscaleOutput.TailscaleIPs)\n\n\treturn &Info{BackendState: tailscaleOutput.BackendState, IPv4Address: net.ParseIP(ipv4Address), IPv6Address: net.ParseIP(ipv6Address), NodeID: \"\", ProviderName: \"tailscale\", Interface: tailscaleIf}, nil\n}\n\n// get Tailscale advertised route list\nfunc GetAdvertisedRoutes() ([]netip.Prefix, error) {\n\toutput, err := util.ExecCommand(\"tailscale\", []string{\"debug\", \"prefs\"})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to run tailscale debug prefs: %v\", err)\n\t}\n\n\tlogrus.Debugf(\"Output from tailscale debug prefs: %v\", output)\n","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/k3s-io/k3s/blob/6ba341e396edc16b8dcae978a7c5e3ac7ee5606e/pkg/vpn/vpn.go#L153-L189","documentation":"The stdout of `tailscale status --json` is unmarshalled into TailscaleOutput{TailscaleIPs, BackendState}. If that output is not valid JSON for the shape - the CLI printed an error or warning instead, the output was empty, or a version changed the payload - json.Unmarshal fails and the error is wrapped here.","triggerScenarios":"tailscale exits 0 but writes non-JSON to stdout (warnings, banners, error text); an empty output; a CLI version whose `status --json` payload differs from the expected fields.","commonSituations":"Upgrading or downgrading the tailscale CLI without restarting tailscaled; distributions that patch tailscale; environments where the CLI wraps or pollutes stdout.","solutions":["Run tailscale status --json by hand and pipe through jq . to see what is actually returned","Align the CLI to a known-good version and restart tailscaled after the change","If leading warnings pollute the output, fix the underlying warning (often permissions or stale state) first"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// pre-flight: confirm the CLI emits the JSON shape you expect\nout, err := exec.Command(\"tailscale\", \"status\", \"--json\").Output()\nif err != nil {\n\treturn fmt.Errorf(\"tailscale status failed: %w\", err)\n}\nvar probe struct{ BackendState string `json:\"BackendState\"` }\nif err := json.Unmarshal(out, &probe); err != nil {\n\treturn fmt.Errorf(\"unexpected tailscale output %q: %w\", out, err)\n}","typeGuard":null,"tryCatchPattern":"info, err := vpn.GetInfo(vpnAuth)\nif err != nil {\n\tif strings.Contains(err.Error(), \"failed to unmarshal tailscale output\") {\n\t\t// run `tailscale status --json | jq .` by hand; usually version drift or\n\t\t// non-JSON warnings on stdout - align the CLI version and restart tailscaled\n\t\tlog.Printf(\"raw tailscale output needs inspection\")\n\t}\n\treturn nil, err\n}","preventionTips":["Pin the tailscale CLI version and restart tailscaled after upgrades","Manually verify `tailscale status --json | jq .` after any CLI change","Watch the tailscale release notes - output payloads do change between versions"],"tags":["go","vpn","tailscale","json","parsing"],"backgroundTag":null,"analyzedSha":"6ba341e396edc16b8dcae978a7c5e3ac7ee5606e","analyzedAt":"2026-08-15T16:27:54.286Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}