JuliusBrussee/caveman · error

kms: create request

Error message

kms: create %s request: %w

What it means

call builds the Key Manager endpoint URL and creates the request with http.NewRequestWithContext; if request construction fails, the error is wrapped as "kms: create <operation> request: %w". NewRequestWithContext only fails on an unparseable URL or an invalid method, so this points to a malformed apiBaseURL in the client configuration.

Solutions

  1. Inspect and fix the apiBaseURL used at construction — it must be an absolute, valid URL like https://api.scaleway.com.
  2. Trim/expand the env var or config value feeding apiBaseURL (no newlines, no unexpanded templates).
  3. Validate the URL with url.Parse before constructing the client and fail fast on bad config.
  4. Unwrap the error chain for the exact url.Parse failure message.

Example fix

// before
client := &kms.Client{apiBaseURL: os.Getenv("SCW_API"), ...} // SCW_API=""
_, err := client.Encrypt(ctx, pt) // kms: create encrypt request: ...
// after
base := strings.TrimSpace(os.Getenv("SCW_API"))
if u, err := url.Parse(base); err != nil || u.Scheme == "" || u.Host == "" {
    return fmt.Errorf("invalid SCW_API base URL %q", base)
}
client := &kms.Client{apiBaseURL: base, ...}
Defensive patterns

Strategy: validation

Validate before calling

base := strings.TrimSpace(cfg.APIBaseURL)
if u, err := url.Parse(base); err != nil || u.Scheme == "" || u.Host == "" {
    return fmt.Errorf("apiBaseURL %q is not an absolute URL", base)
}

Type guard

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

Try / catch

_, err := client.Encrypt(ctx, plaintext)
if err != nil {
    if strings.Contains(err.Error(), "create encrypt request") {
        return fmt.Errorf("malformed KMS endpoint config: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Encrypt or Decrypt when c.apiBaseURL is empty, contains spaces/control characters, or is otherwise unparseable, making the concatenation an invalid URL; the operation path component itself is escaped, so the base URL is the usual culprit.

Common situations: Configuring apiBaseURL from an env var with a trailing newline or whitespace; missing scheme (e.g. "scaleway.api" instead of "https://..."); config templating leaving a placeholder like "{{API_URL}}" unexpanded.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/53e7ec0bebbacc2f. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/kms/kms.go:355

	if !regionPattern.MatchString(region) {
		return errors.New("kms: invalid Scaleway region")
	}
	if !keyIDPattern.MatchString(keyID) {
		return errors.New("kms: invalid Scaleway key ID")
	}
	return nil
}

func (c *Client) call(ctx context.Context, region, keyID, operation string, input, output any) error {
	body, err := json.Marshal(input)
	if err != nil {
		return fmt.Errorf("kms: encode %s request: %w", operation, err)
	}
	endpoint := c.apiBaseURL + "/key-manager/v1alpha1/regions/" + url.PathEscape(region) +
		"/keys/" + url.PathEscape(keyID) + "/" + operation
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("kms: create %s request: %w", operation, err)
	}
	req.Header.Set("content-type", "application/json")
	req.Header.Set("accept", "application/json")
	req.Header.Set("x-auth-token", c.token)
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("kms: %s request failed: %w", operation, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 32<<10))
		return fmt.Errorf("kms: %s returned HTTP %d", operation, resp.StatusCode)
	}
	data, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes+1))
	if err != nil {
		return fmt.Errorf("kms: read %s response: %w", operation, err)
	}
	if len(data) > maxResponseBytes {

View on GitHub (pinned to 3ee70a1026)