gastownhall/beads · error

failed to create request

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.

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.

Appendix: 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)