juanfont/headscale · error

%s creating confirm request: %w

Error message

%s creating confirm request: %w

What it means

`http.NewRequestWithContext` failed while building the POST that auto-submits the confirm form. This is Go's standard request constructor complaining about the assembled URL (scheme/host/path) or the body reader — the confirm URL built from `prevResp.Request.URL` plus the scraped form action was not parseable.

Source

Thrown at integration/scenario.go:1314

	// Build the absolute POST URL from the response's request URL.
	base := prevResp.Request.URL
	confirmURL := &url.URL{
		Scheme: base.Scheme,
		Host:   base.Host,
		Path:   formAction,
	}

	log.Printf("%s auto-submitting confirm form: %s", hostname, confirmURL)

	formData := url.Values{
		"headscale_register_confirm": {csrfToken},
	}

	ctx := context.Background()

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, confirmURL.String(), strings.NewReader(formData.Encode()))
	if err != nil {
		return "", nil, fmt.Errorf("%s creating confirm request: %w", hostname, err)
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	confirmResp, err := hc.Do(req)
	if err != nil {
		return "", nil, fmt.Errorf("%s sending confirm request: %w", hostname, err)
	}
	defer confirmResp.Body.Close()

	confirmBytes, err := io.ReadAll(confirmResp.Body)
	if err != nil {
		return "", nil, fmt.Errorf("%s reading confirm response: %w", hostname, err)
	}

	if confirmResp.StatusCode != http.StatusOK {
		return string(confirmBytes), nil, fmt.Errorf( //nolint:err113
			"%s confirm returned status %d: %s",

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Log `confirmURL.String()` right before building the request and eyeball scheme/host/path.
  2. Sanitize the scraped action: `html.UnescapeString(formAction)` and `url.PathUnescape` as appropriate.
  3. Fall back to `base.ResolveReference(parsedAction)` instead of manually copying Scheme/Host/Path so relative actions resolve correctly.

Example fix

// before
confirmURL := &url.URL{Scheme: base.Scheme, Host: base.Host, Path: formAction}

// after
ref, err := url.Parse(html.UnescapeString(formAction))
if err != nil {
    return "", nil, fmt.Errorf("%s confirm form: bad action URL %q: %w", hostname, formAction, err)
}
confirmURL := base.ResolveReference(ref)
Defensive patterns

Strategy: validation

Validate before calling

// Validate the action before building the request.
ref, err := url.Parse(html.UnescapeString(formAction))
if err != nil || ref.Path == "" {
    return "", nil, fmt.Errorf("%s confirm form: unusable action %q", hostname, formAction)
}

Prevention

When it happens

Trigger: `confirmURL.String()` is malformed — e.g. the scraped `formAction` contains characters that make the URL invalid for `http.NewRequestWithContext`, or scheme/host are empty because `prevResp.Request` was nil-adjacent/mis-set.

Common situations: The form action scrape picked up garbage (HTML-escaped characters like &); a code path where `prevResp` is not the page that rendered the form; localhost URL built with an empty Host.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/610d1aafca266174. Report an issue: GitHub.