The Problem
GitHub serves repository archive downloads (the tar.gz and zip snapshots
that workflows like actions/checkout resolve to) from a separate host:
codeload.github.com. A request looks like
GET https://codeload.github.com/actions/checkout/tar.gz/34e114876b0b11c390a56381ad16ebd13914f8d5
For a CI pipeline that runs the same actions/checkout@v4 reference thousands
of times a day, every job ends up pulling the same tarball — keyed by an
immutable commit SHA — across the corporate network egress. When that egress
is constrained (proxy outages, bandwidth caps, rate limiting) builds either
slow down or fail outright with errors like:
Failed to download action 'https://codeload.github.com/actions/checkout/tar.gz/<sha>'.
Error: The proxy tunnel request to proxy 'http://proxy.example.com:3128/' failed
with status code '503'.
Because the archive content for a given (owner, repo, sha) triple never
changes, this traffic is highly cacheable. ghp can steer codeload requests to
an internal caching mirror so the first request fetches once from GitHub and
every subsequent request hits the mirror.
How It Works
When codeload.github.com resolves to ghp (typically via DNS or an HTTP proxy
configured to forward this host), ghp inspects each request:
/{owner}/{repo}/{format}/{ref}
where format is one of tar.gz, zip, legacy.tar.gz, or legacy.zip,
and ref is a commit SHA, branch, or tag.
When codeload.redirect_to is configured, a matching request is answered
immediately with 302 Found to redirect_to + path, preserving the original
query string. Non-matching paths and requests for orgs or org/repo pairs in
the allow list are forwarded transparently to upstream codeload.github.com
so other tooling continues to work.
ghp does not cache anything itself — it offloads the cache to a mirror you already operate on the corporate network. The expected mirror behaviour is straightforward:
- On first request for
(owner, repo, ref), fetch the archive fromcodeload.github.comand store it. - On subsequent requests for the same
(owner, repo, ref), serve from storage.
Cacheability depends on what ref resolves to. When ref is a commit
SHA (the common case for actions/checkout@<sha> and any pinned action
reference) the (owner, repo, sha) tuple identifies an immutable archive
and the mirror can cache it indefinitely. When ref is a branch or tag
name, GitHub resolves it on each fetch and the underlying commit can move
— mirrors should treat those entries as mutable (e.g. apply a short TTL,
revalidate against codeload.github.com, or serve only SHA refs from
cache). The redirect itself is opaque to ghp; the cache policy is the
mirror's responsibility.
Configuration
codeload:
redirect_to: "https://codeload.cache.example.com/"
allow:
- "myorg" # all repos under this org bypass the redirect
- "external/tool" # only this specific repo bypasses
Or via environment variables:
GHP_CODELOAD_REDIRECT_TO=https://codeload.cache.example.com/
GHP_CODELOAD_ALLOW=myorg,external/tool
Indexed allow lists are supported for orchestrators that cannot pass comma-separated values reliably:
GHP_CODELOAD_REDIRECT_TO=https://codeload.cache.example.com/
GHP_CODELOAD_ALLOW_COUNT=2
GHP_CODELOAD_ALLOW_0=myorg
GHP_CODELOAD_ALLOW_1=external/tool
When redirect_to is empty the codeload handler is effectively a transparent
proxy — every request is forwarded to upstream codeload.github.com. This
means simply pointing codeload.github.com at ghp does not break anything
even before the redirect is configured.
The redirect_to value must be an absolute URL (scheme + host); a relative
path causes ghp to log an error and fall back to passthrough. redirect_to
and the allow list are reloaded on SIGUSR1 along with the rest of the
hot-reloadable configuration.
Monitoring
Each archive request handled by the codeload handler increments the counter:
ghp_codeload_redirect_total— labeled byowner,repo,archive(one oftar.gz,zip,legacy.tar.gz,legacy.zip), andresult(redirectorpassthrough).
The full ref (SHA, branch, or tag) is intentionally not a metric label:
including a SHA would create unbounded label cardinality. The full request URL
— and therefore the ref — is recorded in the JSON access log alongside the
backend (codeload.github.com), so per-SHA analysis is still possible via log
queries.
Use this metric to validate cache hit rates against your mirror's own counters
and to spot which (owner, repo) pairs dominate egress.
Limitations
-
Detection is path-based. The handler matches the URL pattern used by GitHub's archive download mechanism. If GitHub changes its URL structure in future, the regex would need updating.
-
Authentication is not forwarded on redirect. A 302 strips the
Authorizationheader in most clients, so private-repo archives served by the mirror require the mirror to handle authentication itself or be on a trusted internal network. The transparent passthrough path (allow list and noredirect_to) preserves the original request, including its Authorization header. -
No HEAD availability check. Unlike the release redirect feature, the codeload handler does not probe the mirror before redirecting. The expected mirror is a transparent caching proxy — fetching from upstream on cache miss — so a pre-flight check would always succeed and just add latency. If you operate a selective mirror that only stores a curated subset of archives, file an issue describing the use case.