gastownhall/beads · error

failed to create request: %w

Error message

failed to create request: %w

What it means

executeOnce builds the outbound POST with http.NewRequestWithContext against the client's Endpoint. If request construction fails (typically a malformed or unparseable URL/endpoint), it returns 'failed to create request' wrapped around the underlying error. This happens before sending, so no HTTP exchange occurred.

Source

Thrown at internal/linear/client.go:366

// executeOnce performs the actual HTTP request loop with rate-limit retries.
// Returns the response data, the last HTTP status code encountered, and any error.
func (c *Client) executeOnce(ctx context.Context, req *GraphQLRequest) (json.RawMessage, int, error) {
	body, err := json.Marshal(req)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to marshal request: %w", err)
	}

	var lastErr error
	var lastStatus int
	for attempt := 0; attempt <= MaxRetries; attempt++ {
		if rlErr := c.circuitBreakerError(); rlErr != nil {
			return nil, lastStatus, rlErr
		}

		httpReq, err := http.NewRequestWithContext(ctx, "POST", c.Endpoint, bytes.NewReader(body))
		if err != nil {
			return nil, 0, fmt.Errorf("failed to create request: %w", err)
		}

		httpReq.Header.Set("Content-Type", "application/json")
		authValue, err := c.authHeader()
		if err != nil {
			return nil, 0, err
		}
		httpReq.Header.Set("Authorization", authValue)

		resp, err := c.HTTPClient.Do(httpReq)
		if err != nil {
			lastErr = fmt.Errorf("request failed (attempt %d/%d): %w", attempt+1, MaxRetries+1, err)
			continue
		}

		respBody, err := io.ReadAll(io.LimitReader(resp.Body, MaxResponseSize))
		_ = resp.Body.Close() // Best effort: HTTP body close; connection may be reused regardless
		if err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Print and inspect the wrapped error plus the configured Endpoint value
  2. Set Endpoint to a valid absolute URL, e.g. https://api.linear.app/graphql
  3. Check the env var/config feeding Endpoint for empty values or stray characters/whitespace
  4. Quote the endpoint when exporting it in shell to avoid truncation

Example fix

// before
client := &linear.Client{Endpoint: os.Getenv("LINEAR_ENDPOINT")} // may be ""
// after
endpoint := os.Getenv("LINEAR_ENDPOINT")
if endpoint == "" { endpoint = "https://api.linear.app/graphql" }
if _, err := url.Parse(endpoint); err != nil { return err }
client := &linear.Client{Endpoint: endpoint}
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(endpoint)
if err != nil || u.Scheme == "" || u.Host == "" {
	return fmt.Errorf("invalid Linear endpoint %q", endpoint)
}

Type guard

func validEndpoint(s string) bool {
	u, err := url.Parse(s)
	return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != "" && url.PathEscape(s) == s || (u.Scheme == "https" && u.Host != "")
}

Try / catch

data, err := client.Execute(ctx, req)
if err != nil {
	if strings.Contains(err.Error(), "failed to create request") {
		return fmt.Errorf("misconfigured endpoint %q: %w", endpoint, err)
	}
	return err
}

Prevention

When it happens

Trigger: Any Execute call where c.Endpoint is empty, contains spaces/control characters, or otherwise fails http.NewRequestWithContext URL parsing (url.Parse error).

Common situations: Misconfigured LINEAR endpoint env var (empty, typo like 'linear.api.com' without scheme is actually fine for Parse, but a URL with spaces or invalid characters is not); config interpolation that produced an empty endpoint; environment variable truncated by shell quoting.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/962ae452f1306b2b. Report an issue: GitHub.