tailscale/tailscale · error

failed to find latest Tailscale version in /boot/config/plug

Error message

failed to find latest Tailscale version in /boot/config/plugins/tailscale.plg: %w

What it means

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.

Source

Thrown at clientupdate/clientupdate.go:1217

		}
	}()

	// We need to run `plugin check` for the latest tailscale.plg to get
	// downloaded. Unfortunately, the output of this command does not contain
	// the latest tailscale version available. So we'll parse the downloaded
	// tailscale.plg file manually below.
	out, err := exec.Command("plugin", "check", "tailscale.plg").CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to check if Tailscale plugin is upgradable: %w, output: %q", err, out)
	}

	// Note: 'plugin check' downloads plugins to /tmp/plugins.
	// The installed .plg files are in /boot/config/plugins/, but the pending
	// ones are in /tmp/plugins. We should parse the pending file downloaded by
	// 'plugin check'.
	latest, err := parseUnraidPluginVersion("/tmp/plugins/tailscale.plg")
	if err != nil {
		return fmt.Errorf("failed to find latest Tailscale version in /boot/config/plugins/tailscale.plg: %w", err)
	}
	if !up.confirm(latest) {
		return nil
	}

	up.Logf("c2n: running 'plugin update tailscale.plg'")
	cmd := exec.Command("plugin", "update", "tailscale.plg")
	cmd.Stdout = up.Stdout
	cmd.Stderr = up.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("failed tailscale plugin update: %w", err)
	}
	return nil
}

func parseUnraidPluginVersion(plgPath string) (string, error) {
	plg, err := os.ReadFile(plgPath)
	if err != nil {

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Inspect the staged file manually: cat /tmp/plugins/tailscale.plg and look for the <FILE Name=...> line
  2. Re-run 'plugin check tailscale.plg' and immediately retry the update so /tmp content is fresh
  3. If the FILE line format changed, update manually (plugin check tailscale.plg && plugin update tailscale.plg) and report the format change upstream
  4. Check /tmp is writable and was not wiped mid-update
Defensive patterns

Strategy: validation

Validate before calling

// Verify the staged .plg file exists and parses before updating
func unraidPlgStaged() (string, bool) {
	plg, err := os.ReadFile("/tmp/plugins/tailscale.plg")
	if err != nil { return "", false }
	m := regexp.MustCompile(`<FILE Name="/boot/config/plugins/tailscale/tailscale_(\d+\.\d+\.\d+)_[a-z0-9]+\.tgz">`).FindStringSubmatch(string(plg))
	if len(m) < 2 { return "", false }
	return m[1], true
}

Type guard

func isPlgVersionParseErr(err error) bool {
	return err != nil && strings.Contains(err.Error(), "failed to find latest Tailscale version in /boot/config/plugins/tailscale.plg")
}

Try / catch

if err := up.Update(ctx); err != nil {
	if isPlgVersionParseErr(err) {
		// re-stage the descriptor then retry: plugin check downloads it to /tmp/plugins
		if cerr := exec.Command("plugin", "check", "tailscale.plg").Run(); cerr == nil {
			return up.Update(ctx)
		}
	}
	return err
}

Prevention

When it happens

Trigger: '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).

Common situations: /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.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/64eaa1a3be7542b4. Report an issue: GitHub.