JuliusBrussee/caveman · error
githubapp: invalid request path
Error message
githubapp: invalid request path
What it means
After the basic single-host-path check, do() parses the request path with url.ParseRequestURI and rejects anything that is absolute (has a scheme), carries a host, or contains userinfo. The path argument must be purely relative. This error fires when the path argument is actually a full URL or otherwise unparseable.
Solutions
- Pass only the relative path portion, e.g. "/repos/octocat/hello", and keep the host in the App's baseURL.
- If you have a full URL, strip the scheme+host before calling, or construct the App with that base instead.
- Encode query parameters with url.Values and interpolate via "?" + v.Encode() so no raw spaces/unsafe chars reach the path.
- Trim stray whitespace/control characters from the path before calling.
Example fix
// before client.DoToken(ctx, tok, "GET", "https://api.github.com/repos/octo/hello", nil) // after client.DoToken(ctx, tok, "GET", "/repos/octo/hello", nil)
Defensive patterns
Strategy: validation
Validate before calling
u, err := url.ParseRequestURI(path)
if err != nil || u.IsAbs() || u.Host != "" || u.User != nil {
return fmt.Errorf("path must be relative: %q", path)
} Try / catch
if _, _, err := app.DoToken(ctx, tok, m, path, nil); err != nil {
if strings.Contains(err.Error(), "invalid request path") {
// path was a full URL; strip scheme+host and retry once
}
} Prevention
- Keep a single path-builder helper that only emits relative paths.
- When migrating from full-URL clients, strip the base at the boundary.
- Encode query strings with url.Values.Encode().
When it happens
Trigger: Passing "https://api.github.com/repos/o/r" as the path to DoToken; passing "mailto:x@y" or a path with embedded credentials like "//user@host/x"; passing a string with characters that break ParseRequestURI (e.g. raw spaces or control characters).
Common situations: Refactoring code that previously used a full-URL HTTP client and passing the whole URL as the path; concatenating a base URL into the path variable, producing a double-prefixed value; pasting a curl URL directly as the path argument.
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
- githubapp: request path must be a single-host absolute path
- githubapp: request path escaped configured host
- awscreds: AWS_EC2_METADATA_SERVICE_ENDPOINT is not a valid…
- githubapp: invalid base URL
- ssrf: credentials embedded in URL are forbidden
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/dad02bcd2592b6d8.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/githubapp/githubapp.go:358
// DoToken issues an authenticated GitHub REST call with an installation token and
// returns the status + raw body for the caller to parse. It is the reusable
// primitive the worker's PR opener builds the Git Data API flow on, so every
// GitHub egress goes through the one SSRF-guarded client + fixed base host.
func (a *App) DoToken(ctx context.Context, token, method, path string, body any) (int, []byte, error) {
return a.do(ctx, "Bearer "+token, method, path, body)
}
func (a *App) do(ctx context.Context, authorization, method, path string, body any) (int, []byte, error) {
if !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") || strings.Contains(path, "\\") {
return 0, nil, fmt.Errorf("githubapp: request path must be a single-host absolute path")
}
base, err := url.Parse(a.baseURL)
if err != nil || base.Scheme == "" || base.Host == "" || base.User != nil {
return 0, nil, fmt.Errorf("githubapp: invalid base URL")
}
relative, err := url.ParseRequestURI(path)
if err != nil || relative.IsAbs() || relative.Host != "" || relative.User != nil {
return 0, nil, fmt.Errorf("githubapp: invalid request path")
}
target, err := url.Parse(a.baseURL + path)
if err != nil || target.Scheme != base.Scheme || !strings.EqualFold(target.Host, base.Host) || target.User != nil {
return 0, nil, fmt.Errorf("githubapp: request path escaped configured host")
}
var reader io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return 0, nil, fmt.Errorf("githubapp: marshal request: %w", err)
}
reader = bytes.NewReader(b)
}
req, err := http.NewRequestWithContext(ctx, method, target.String(), reader)
if err != nil {
return 0, nil, fmt.Errorf("githubapp: build request: %w", err)
}
req.Header.Set("Authorization", authorization)View on GitHub (pinned to 3ee70a1026)