golang/go · error

internal error: failed to construct url: %v

Error message

internal error: failed to construct url: %v

What it means

Returned by doPkgsite when url.JoinPath("http://"+addr, urlPath) fails while constructing the documentation URL. The error is explicitly prefixed 'internal error' because, given the preceding pickUnusedPort success and the expected shape of urlPath, JoinPath should not fail — its failure indicates a malformed caller-supplied urlPath (control characters, bad escaping).

Source

Thrown at src/cmd/go/internal/doc/pkgsite.go:112

	b.Do(ctx, a)

	// Both paths return an executable in GOCACHE: CachedExecutable is set on
	// fresh builds, while BuiltTarget is set on cache hits.
	if cached := a.CachedExecutable(); cached != "" {
		return cached
	}
	return a.BuiltTarget()
}

func doPkgsite(ctx context.Context, urlPath, fragment string) error {
	port, err := pickUnusedPort()
	if err != nil {
		return fmt.Errorf("failed to find port for documentation server: %v", err)
	}
	addr := fmt.Sprintf("localhost:%d", port)
	path, err := url.JoinPath("http://"+addr, urlPath)
	if err != nil {
		return fmt.Errorf("internal error: failed to construct url: %v", err)
	}
	if fragment != "" {
		path += "#" + fragment
	}

	if file := os.Getenv("TEST_GODOC_URL_FILE"); file != "" {
		return os.WriteFile(file, []byte(path+"\n"), 0666)
	}

	// Turn off the default signal handler for SIGINT (and SIGQUIT on Unix)
	// and instead wait for the child process to handle the signal and
	// exit before exiting ourselves.
	base.StartSigHandlers()

	// Prepend the local download cache to GOPROXY to get around deprecation checks.
	env := os.Environ()
	vars, err := runCmd(env, goCmd(), "env", "GOPROXY", "GOMODCACHE")
	fields := strings.Fields(vars)

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Sanitize urlPath to a plain relative path before calling doPkgsite.
  2. Pass any fragment via the dedicated `fragment` argument, not inside urlPath.
  3. Report it as a go bug if reached with normal `go doc` usage.
  4. As a workaround, invoke pkgsite directly.

Example fix

// before: urlPath carries a fragment or full URL -> JoinPath fails
path, err := url.JoinPath("http://"+addr, urlPath) // urlPath="http://x/#y"

// after: keep urlPath relative; pass fragment separately
path, err := url.JoinPath("http://"+addr, strings.TrimPrefix(urlPath, "/"))
if fragment != "" { path += "#" + fragment }
Defensive patterns

Strategy: validation

Validate before calling

// strip scheme and fragments; pass fragment separately
rel := strings.TrimPrefix(urlPath, "http://")
rel = strings.TrimPrefix(rel, "https://")
if i := strings.IndexByte(rel, '#'); i >= 0 {
    fragment = rel[i+1:]
    rel = rel[:i]
}
// then build the URL safely

Type guard

func isSafeUrlPath(p string) bool {
    return !strings.ContainsAny(p, ":#\n\r") && !strings.HasPrefix(p, "http")
}

Prevention

When it happens

Trigger: urlPath contains characters/sequences that url.JoinPath rejects (e.g. an absolute URL, control chars, or a malformed fragment baked into urlPath instead of the fragment argument). In normal use this should be unreachable.

Common situations: A bug in how go doc derives urlPath from user args (e.g. passing a full URL or a path with embedded newlines); future refactors that violate JoinPath's invariants.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/7edef2b8bc686cf6. Report an issue: GitHub.