github/github-mcp-server · error
failed to download logs: HTTP %d
Error message
failed to download logs: HTTP %d
What it means
Returned by downloadLogContent (pkg/github/actions.go:179) when the GET of the signed log URL completes but returns a status other than 200 - the body of that response is discarded and only the code is reported. Because GetWorkflowJobLogs redirects to a short-lived signed blob URL, the two dominant causes are an expired signature (403) and a deleted/purged blob (404).
Source
Thrown at pkg/github/actions.go:179
result["message"] = "Job logs are available for download"
result["note"] = "The logs_url provides a download link for the individual job logs in plain text format. Use return_content=true to get the actual log content."
}
return result, resp, nil
}
func downloadLogContent(ctx context.Context, logURL string, tailLines int, maxLines int) (string, int, *http.Response, error) {
prof := profiler.New(nil, profiler.IsProfilingEnabled())
finish := prof.Start(ctx, "log_buffer_processing")
httpResp, err := http.Get(logURL) //nolint:gosec
if err != nil {
return "", 0, httpResp, fmt.Errorf("failed to download logs: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode != http.StatusOK {
return "", 0, httpResp, fmt.Errorf("failed to download logs: HTTP %d", httpResp.StatusCode)
}
bufferSize := min(tailLines, maxLines)
processedInput, totalLines, httpResp, err := buffer.ProcessResponseAsRingBufferToEnd(httpResp, bufferSize)
if err != nil {
return "", 0, httpResp, fmt.Errorf("failed to process log content: %w", err)
}
lines := strings.Split(processedInput, "\n")
if len(lines) > tailLines {
lines = lines[len(lines)-tailLines:]
}
finalResult := strings.Join(lines, "\n")
_ = finish(len(lines), int64(len(finalResult)))
return finalResult, totalLines, httpResp, nilView on GitHub (pinned to 0ea1f775a7)
Solutions
- Do not cache or reuse the logs_url - fetch it fresh via GetWorkflowJobLogs on every attempt
- On 404, accept the logs are purged (retention expired) and re-run the workflow if logs are needed
- On 403 with an error page body, check proxy rules for objects.githubusercontent.com
- Retry once with a fresh URL - expiry clears immediately
Example fix
// before
url, _, _ := client.Actions.GetWorkflowJobLogs(ctx, owner, repo, jobID, 1)
time.Sleep(10 * time.Minute) // URL goes stale
http.Get(url.String()) // -> HTTP 403
// after - always fetch a fresh URL right before downloading
url, _, err := client.Actions.GetWorkflowJobLogs(ctx, owner, repo, jobID, 1)
if err != nil {
return err
}
resp, err := http.Get(url.String()) // use immediately
if err == nil && resp.StatusCode != http.StatusOK {
url, _, _ = client.Actions.GetWorkflowJobLogs(ctx, owner, repo, jobID, 1) // refresh once
resp, err = http.Get(url.String())
} Defensive patterns
Strategy: retry
Validate before calling
// Do not pre-validate a stale URL - validate your flow instead:
// always fetch the URL and download it in the same turn
url, _, err := client.Actions.GetWorkflowJobLogs(ctx, owner, repo, jobID, 1)
if err != nil {
return err
}
resp, err := http.Get(url.String()) // immediately after fetching
_ = resp Try / catch
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == 403 {
url, _, _ = client.Actions.GetWorkflowJobLogs(ctx, owner, repo, jobID, 1) // fresh signature
resp, err = http.Get(url.String())
}
if resp.StatusCode == 404 {
return errors.New("log blob purged (retention expired)") // not retryable
}
} Prevention
- Treat logs URLs as single-use and short-lived
- Export logs promptly after job completion for anything you may need later
- Do not put signed URLs into queues or caches
- On 403, refresh the URL rather than assuming a permission problem
When it happens
Trigger: Delay between obtaining the signed URL and downloading it so the signature expires (403); log blob already garbage-collected after retention expiry (404); rare 5xx from blob storage; any interception layer answering with 4xx before GitHub storage is reached.
Common situations: Retried or cached log URLs reused minutes later; expired retention purging blobs while the API still lists the job; proxies returning 407/403 for storage domains.
Related errors
- failed to download log content for job %d: %w
- unexpected status %d: %s
- failed to marshal response: %w
- failed to get job logs for job %d: %w
- failed to process log content: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/034a30f904bc1821.
Report an issue: GitHub.