v1.0.0
Initial public release.
go-github-kit packages three things most projects re-implement on top of
google/go-github - a conditional-request
ETag cache, the well-known reactive rate limiter from gofri/go-github-ratelimit,
and a client-side token-bucket throttle - behind one options-pattern constructor.
You can adopt the whole stack in a few lines, or import the sub-packages a la
carte if you already have one of these and just want the others.
The headline feature is the ETag layer: GitHub's server-side ETag includes the
Authorization header, so a passive store-and-forward cache loses its hit rate
the moment your token rotates. ghkit reproduces that hash client-side so cached
entries keep working across rotations - durable quota savings for GitHub Apps
and rotating PATs alike.
Added
ETag caching that survives token rotation (etag/)
- Client-side precompute of GitHub's auth-inclusive ETag hash, so 304s keep
flowing across GitHub App installation-token rotations and rotating PATs.
Algorithm originally reverse-engineered by
bored-engineer. - Bounded in-process LRU as the default backend (
etag.NewLRUCache) - defaults
to 4096 entries and a 256 MiB byte budget, both tunable via
etag.WithMaxBodyBytesandetag.WithMaxCacheBytes. - Pluggable
etag.Cacheinterface (Get/Add/Remove, all context-aware)
so you can drop in Redis, S3, bbolt, Pebble, or any other backend without
forking the kit. - Multi-tenant safety via
etag.WithKeyScope(...)- required whenever a cache
is shared across identities, so two installations hitting the same URL can
never read each other's bodies. - Automatic drift detection: every cacheable 200 is verified, and after 10
precompute mismatches inside a 60-second window the transport silently
falls back to passively echoing the server's ETag. After a one-hour
cooldown it probes a small fraction of requests; consecutive successes
restore precompute mode automatically. Wireetag.WithDriftDetected(...)
for an alert hook on each transition; call(*etag.Transport).Stats()for
/healthzor dashboard polling. - Explicit construction-time error if the supplied base transport isn't an
*http.Transport, instead of silently miscomputing every hash. Default
base disables gzip so the hash domain matches what GitHub signed. - Sanitised structured logging on a strict allowlist - no header values, no
hash prefixes, no auth lengths. Records are emitted via slog's*Context
variants (DebugContext,WarnContext,InfoContext,ErrorContext)
so a context-awareslog.Handlercan stamp request IDs onto every line.
The upstreamX-GitHub-Request-Idresponse header is included as
github_request_idfor cross-referencing with GitHub-side debugging.
Reactive rate limiting (ratelimit/)
- Thin facade over
gofri/go-github-ratelimit/v2
with sensible defaults for both GitHub primary and secondary limits. - Curated callbacks for the common observability hooks:
WithPrimaryLimitDetected,WithPrimaryLimitReset,
WithSecondaryLimitDetected,WithTotalSleepLimit,WithLogger. - Escape hatch via
ratelimit.WithUpstreamOptions(opts ...any)for any
upstream feature the kit hasn't curated yet.
Proactive client-side throttling (throttle/)
- Token-bucket throttle (built on
golang.org/x/time/rate) that caps RPS
before GitHub ever sees the request - useful for backfill and batch jobs
that would otherwise burst into secondary limits. - Standalone
throttle.NewTransport(base, rps, opts...)for hand-built
stacks, orghkit.WithRequestsPerSecond(rps, burst)from the top level.
Composable client construction (top-level ghkit package)
ghkit.New(...)- generic factory wrapper that returns whatever client
type your factory produces (*github.Client,githubX.NewClient, or any
func(*http.Client) T). ghkit itself has zero compile-time dependency
ongo-github, so you can pin any major version you like.ghkit.HTTPClient(...)- assemble just the transport stack and hand the
resulting*http.Clientto whichever SDK you prefer.- Options:
WithToken(pat)andWithTokenSource(src oauth2.TokenSource)for static
PATs and JIT auth respectively (works cleanly withghinstallationfor
local-key JWT signing orisometry/ghaitfor KMS-backed signing).WithETagCache(opts ...etag.Option)to plug in the ETag layer, with
the full sub-package option surface forwarded.WithRateLimit(opts ...ratelimit.Option)(default ON) and
WithRateLimitDisabled()for the rare cases you don't want it.WithRequestsPerSecond(rps, burst)for the proactive throttle.WithBaseTransport(rt),WithTimeout(d),WithUserAgent(ua),
WithLogger(l)for the usual transport-shape knobs.
- Layer order is load-bearing and documented: throttle, then rate-limit,
then oauth2, then etag, then the base transport. The kit only assembles
the layers you opt into. - GitHub Enterprise Server supported by passing a custom factory that calls
(*github.Client).WithEnterpriseURLs(...).
Documentation
README.mdwith quick-start, the full transport-stack diagram, recipes for
static PAT, GitHub App installation tokens (withghinstallationand
ghaitadapters), backfill jobs, GitHub Enterprise Server, and using
theetagsub-package on its own.MIGRATION.mdwith three before/after recipes - Kubernetes operator with
rotating PAT, multi-installation webhook processor, backfill/batch job,
plus a verification checklist and notes on behavioural differences worth
spotting before the swap.- Package-level
doc.go, runnableexample_test.go, and pkg.go.dev-rendered
reference for every exported symbol.
Tooling and CI
Makefiletargets:test,test-unit,test-live(live ETag drift
probe againstapi.github.com),test-fuzz(30s fuzz over the ETag
hash),bench,bench-update,lint(golangci-lint v2),
vuln(govulncheck),tidy.- GitHub Actions CI running lint, race-enabled tests, fuzzing, and the live
drift probe - so the day GitHub changes its ETag algorithm we know within
one CI run instead of when users start filing issues. - MIT license.
Dependencies
- Go 1.26.2
github.com/google/go-github/v85v85.0.0github.com/gofri/go-github-ratelimit/v2v2.0.2github.com/hashicorp/golang-lru/v2v2.0.7golang.org/x/oauth2v0.36.0golang.org/x/timev0.15.0