{"record":{"id":"64eaa1a3be7542b4","repo":"tailscale/tailscale","slug":"failed-to-find-latest-tailscale-version-in-boot-c","errorCode":null,"errorMessage":"failed to find latest Tailscale version in /boot/config/plugins/tailscale.plg: %w","messagePattern":"failed to find latest Tailscale version in /boot/config/plugins/tailscale\\.plg: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"clientupdate/clientupdate.go","lineNumber":1217,"sourceCode":"\t\t}\n\t}()\n\n\t// We need to run `plugin check` for the latest tailscale.plg to get\n\t// downloaded. Unfortunately, the output of this command does not contain\n\t// the latest tailscale version available. So we'll parse the downloaded\n\t// tailscale.plg file manually below.\n\tout, err := exec.Command(\"plugin\", \"check\", \"tailscale.plg\").CombinedOutput()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check if Tailscale plugin is upgradable: %w, output: %q\", err, out)\n\t}\n\n\t// Note: 'plugin check' downloads plugins to /tmp/plugins.\n\t// The installed .plg files are in /boot/config/plugins/, but the pending\n\t// ones are in /tmp/plugins. We should parse the pending file downloaded by\n\t// 'plugin check'.\n\tlatest, err := parseUnraidPluginVersion(\"/tmp/plugins/tailscale.plg\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to find latest Tailscale version in /boot/config/plugins/tailscale.plg: %w\", err)\n\t}\n\tif !up.confirm(latest) {\n\t\treturn nil\n\t}\n\n\tup.Logf(\"c2n: running 'plugin update tailscale.plg'\")\n\tcmd := exec.Command(\"plugin\", \"update\", \"tailscale.plg\")\n\tcmd.Stdout = up.Stdout\n\tcmd.Stderr = up.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed tailscale plugin update: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc parseUnraidPluginVersion(plgPath string) (string, error) {\n\tplg, err := os.ReadFile(plgPath)\n\tif err != nil {","sourceCodeStart":1199,"sourceCodeEnd":1235,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/clientupdate/clientupdate.go#L1199-L1235","documentation":"updateUnraid then calls parseUnraidPluginVersion on /tmp/plugins/tailscale.plg (where 'plugin check' stages pending .plg files — the error text names /boot/config/plugins but the code reads /tmp per the comment). The wrapped error is either an os.ReadFile failure (file missing/unreadable) or 'version not found in plg file' when the regexp for <FILE Name=\"/boot/config/plugins/tailscale/tailscale_(\\d+\\.\\d+\\.\\d+)_[a-z0-9]+.tgz\"> does not match.","triggerScenarios":"'plugin check' exited 0 but wrote no tailscale.plg into /tmp/plugins (output went elsewhere or /tmp was cleared between steps), or the .plg file's FILE line changed shape so the version regex misses (layout change in a newer Tailscale .plg).","commonSituations":"/tmp on tmpfs cleared by another process between download and parse; Unraid version differences staging plugins in another directory; Tailscale .plg template updated with a different filename pattern.","solutions":["Inspect the staged file manually: cat /tmp/plugins/tailscale.plg and look for the <FILE Name=...> line","Re-run 'plugin check tailscale.plg' and immediately retry the update so /tmp content is fresh","If the FILE line format changed, update manually (plugin check tailscale.plg && plugin update tailscale.plg) and report the format change upstream","Check /tmp is writable and was not wiped mid-update"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify the staged .plg file exists and parses before updating\nfunc unraidPlgStaged() (string, bool) {\n\tplg, err := os.ReadFile(\"/tmp/plugins/tailscale.plg\")\n\tif err != nil { return \"\", false }\n\tm := regexp.MustCompile(`<FILE Name=\"/boot/config/plugins/tailscale/tailscale_(\\d+\\.\\d+\\.\\d+)_[a-z0-9]+\\.tgz\">`).FindStringSubmatch(string(plg))\n\tif len(m) < 2 { return \"\", false }\n\treturn m[1], true\n}","typeGuard":"func isPlgVersionParseErr(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"failed to find latest Tailscale version in /boot/config/plugins/tailscale.plg\")\n}","tryCatchPattern":"if err := up.Update(ctx); err != nil {\n\tif isPlgVersionParseErr(err) {\n\t\t// re-stage the descriptor then retry: plugin check downloads it to /tmp/plugins\n\t\tif cerr := exec.Command(\"plugin\", \"check\", \"tailscale.plg\").Run(); cerr == nil {\n\t\t\treturn up.Update(ctx)\n\t\t}\n\t}\n\treturn err\n}","preventionTips":["Run 'plugin check tailscale.plg' immediately before the update so /tmp/plugins content is fresh","Do not clean /tmp while an update is in flight on Unraid","If the .plg filename format changes upstream, update the manual runbook: plugin check && plugin update still works without parsing"],"tags":["unraid","plugin","parsing","regexp","filesystem"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}