amir20/dozzle · warning

invalid group

Error message

invalid group

What it means

streamHostGroupLogs reads the `group` URL parameter and unescapes it with url.PathUnescape. If the unescaping fails (malformed percent-encoding such as a stray `%`) or the result is empty, the handler rejects the request with "invalid group" and HTTP 400 Bad Request.

Solutions

  1. Pass a non-empty, correctly URL-encoded group name in the path (encodeURIComponent)
  2. Remove any double encoding; encode once at URL construction
  3. Check the group exists in host labels (dev.dozzle.group) before requesting its stream
  4. Fix malformed `%` sequences in any hand-built URL

Example fix

// before
const url = `/api/hosts/groups/${group}/logs/stream`; // group contains '%' already encoded
// after
const url = `/api/hosts/groups/${encodeURIComponent(group)}/logs/stream`;
Defensive patterns

Strategy: validation

Validate before calling

if (!group || /%(?![0-9A-Fa-f]{2})/.test(group)) throw new Error("invalid group path parameter");
const path = `/api/hosts/groups/${encodeURIComponent(group)}/logs/stream`;

Prevention

When it happens

Trigger: GET /api/hosts/groups/{group}/logs/stream where group contains invalid percent-encoding like `%zz`, or the route is hit with an empty group segment. Both conditions map to this single 400 response.

Common situations: Client-side encodeURIComponent applied twice leaving raw `%` sequences; manually crafted URLs with truncated percent escapes; missing group path segment when building the URL programmatically; renamed groups while old bookmarks keep stale/empty paths.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/2347be95a38e3728. Report an issue: GitHub.

Appendix: source

Thrown at internal/web/logs.go:345

			}
		}

		return len(labelFilters) > 0
	})
}

func (h *handler) streamGroupedLogs(w http.ResponseWriter, r *http.Request) {
	group := chi.URLParam(r, "group")

	h.streamLogsForContainers(w, r, func(container *container.Container) bool {
		return container.State == "running" && container.Group == group
	})
}

func (h *handler) streamHostGroupLogs(w http.ResponseWriter, r *http.Request) {
	group, err := url.PathUnescape(chi.URLParam(r, "group"))
	if err != nil || group == "" {
		http.Error(w, "invalid group", http.StatusBadRequest)
		return
	}

	hostIDs := make(map[string]struct{})
	for _, host := range h.hostService.Hosts() {
		if host.Group == group {
			hostIDs[host.ID] = struct{}{}
		}
	}

	h.streamLogsForContainers(w, r, func(c *container.Container) bool {
		_, ok := hostIDs[c.Host]
		return c.State == "running" && ok
	})
}

func (h *handler) streamHostLogs(w http.ResponseWriter, r *http.Request) {
	host := hostKey(r)

View on GitHub (pinned to d9463cbe21)