Context
src/selfhost/redis-token-cache.ts's get() (lines 22-33ish) wraps its redis.get call in try/catch, with a comment explicitly justifying why: "the caller (github/app.ts's readCachedToken -> createInstallationToken) has no try/catch of its own, so an uncaught error here would hard-fail GitHub App token minting on every Redis hiccup instead of just costing one extra real mint." set() (the sibling function right below it) has no try/catch at all around its redis.set call.
Confirmed the caller genuinely has no surrounding try/catch: src/github/app.ts:341 calls await writeCachedToken(installationId, { token: payload.token, expiresAtMs }) directly, right after successfully obtaining a fresh token from GitHub. A transient Redis write failure at that point throws uncaught and fails the whole createInstallationToken call — turning an otherwise-successful token mint into a hard failure, exactly the failure mode get()'s own comment describes and guards against, just on the write side instead of the read side.
Requirements
- Wrap
set()'s redis.set(...) call in try/catch, matching get()'s fail-open contract: on error, record a metric (via the existing recordTokenCacheMetric used by get()) and return without rethrowing.
- Do not change
get() or the TTL-flooring logic above the redis.set call — this issue is scoped to the one unguarded write call.
- A cache-write failure must never surface as a token-mint failure — the token was already successfully obtained from GitHub before
set() is called.
Deliverables
Test Coverage Requirements
99%+ Codecov patch coverage on every changed line and branch, plus the regression test above.
Expected Outcome
A transient Redis write failure during token caching no longer fails an otherwise-successful GitHub App token mint — set() fails open exactly like its get() sibling already does.
Links & Resources
src/selfhost/redis-token-cache.ts — get()'s existing fail-open pattern (the precedent to mirror into set()). src/github/app.ts:341 — the uncaught caller.
Context
src/selfhost/redis-token-cache.ts'sget()(lines 22-33ish) wraps itsredis.getcall in try/catch, with a comment explicitly justifying why: "the caller (github/app.ts'sreadCachedToken->createInstallationToken) has no try/catch of its own, so an uncaught error here would hard-fail GitHub App token minting on every Redis hiccup instead of just costing one extra real mint."set()(the sibling function right below it) has no try/catch at all around itsredis.setcall.Confirmed the caller genuinely has no surrounding try/catch:
src/github/app.ts:341callsawait writeCachedToken(installationId, { token: payload.token, expiresAtMs })directly, right after successfully obtaining a fresh token from GitHub. A transient Redis write failure at that point throws uncaught and fails the wholecreateInstallationTokencall — turning an otherwise-successful token mint into a hard failure, exactly the failure modeget()'s own comment describes and guards against, just on the write side instead of the read side.Requirements
set()'sredis.set(...)call in try/catch, matchingget()'s fail-open contract: on error, record a metric (via the existingrecordTokenCacheMetricused byget()) and return without rethrowing.get()or the TTL-flooring logic above theredis.setcall — this issue is scoped to the one unguarded write call.set()is called.Deliverables
set()'sredis.setcall is wrapped in try/catch, fail-open, metric-recordedcreateInstallationTokenstill succeeds (returns the already-minted token) instead of throwingTest Coverage Requirements
99%+ Codecov patch coverage on every changed line and branch, plus the regression test above.
Expected Outcome
A transient Redis write failure during token caching no longer fails an otherwise-successful GitHub App token mint —
set()fails open exactly like itsget()sibling already does.Links & Resources
src/selfhost/redis-token-cache.ts—get()'s existing fail-open pattern (the precedent to mirror intoset()).src/github/app.ts:341— the uncaught caller.