MHSanaei/3x-ui · error
latest panel release tag is empty
Error message
latest panel release tag is empty
What it means
Returned by fetchLatestPanelVersion when the GitHub releases API call succeeded (HTTP 200, JSON decoded) but the tag_name field is empty. The API contract expects every release object to carry tag_name, so an empty value means the response shape changed, a proxy rewrote the body, or a JSON decode partially matched an unexpected payload. It is a sanity guard before the tag is used for update logic.
Source
Thrown at internal/web/service/panel/panel.go:408
return "", fmt.Errorf("panel updater download is empty")
}
if n > maxPanelUpdaterBytes {
return "", fmt.Errorf("panel updater exceeds %d bytes", maxPanelUpdaterBytes)
}
if err := file.Chmod(0o700); err != nil {
return "", err
}
ok = true
return path, nil
}
func fetchLatestPanelVersion() (string, error) {
release, err := fetchPanelRelease("")
if err != nil {
return "", err
}
if release.TagName == "" {
return "", fmt.Errorf("latest panel release tag is empty")
}
return release.TagName, nil
}
// fetchPanelRelease fetches a release from GitHub. An empty tag resolves the
// latest stable release; a non-empty tag (e.g. dev-latest) resolves that tag.
func fetchPanelRelease(tag string) (*service.Release, error) {
url := "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest"
if tag != "" {
url = "https://api.github.com/repos/MHSanaei/3x-ui/releases/tags/" + tag
}
client := (&service.SettingService{}).NewProxiedHTTPClient(10 * time.Second)
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if reqErr != nil {
return nil, reqErr
}
resp, err := client.Do(req)
if err != nil {View on GitHub (pinned to ad32144c42)
Solutions
- curl -s https://api.github.com/repos/MHSanaei/3x-ui/releases/latest | jq .tag_name from the same host to see the raw payload
- Bypass or reconfigure the outbound proxy so the genuine GitHub API response arrives
- Update the panel to the latest version in case the field handling was fixed upstream
Defensive patterns
Strategy: validation
Validate before calling
var release service.Release
if err := json.NewDecoder(resp.Body).Decode(&release); err == nil {
if release.TagName == "" {
log.Printf("release payload missing tag_name; body shape changed or proxy rewrote it")
}
}
Type guard
func hasTagName(r *service.Release) bool {
return r != nil && r.TagName != ""
}
Prevention
- Validate required fields of third-party API responses at the boundary
- Log the raw payload when contract guards fire, to distinguish proxy rewriting from API change
- Pin update flows to API versions/shapes you have tested against
When it happens
Trigger: GET https://api.github.com/repos/MHSanaei/3x-ui/releases/latest returns 200 with a body lacking tag_name; an intercepting proxy returns a 200 JSON status page; a GitHub API change renames the field.
Common situations: Man-in-the-middle / filtering proxies that answer API URLs with their own JSON; using an API mirror with a different schema; running an ancient build against a future GitHub API change.
Related errors
- xray version %q is not in the fetched release list
- The new username and password are empty
- ❌ Failed to get inbounds.
- tg_id must be a positive integer
- token not found
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/624014c613b376c9.
Report an issue: GitHub.