19 grumpies found today. Every single one of them is a recurring pattern — nothing has been fixed since yesterday. The champion, token-expiry-check-inconsistency, has now appeared in 25 consecutive daily reviews. I'm no longer grumpy; I'm resigned. One new pattern added: maintenance-startcleanup-bare-slog.
📋 Full Grumpy Report
Review Overview
| Metric |
Value |
| Files Reviewed |
24 source + 22 test files |
| Grumpies Found |
19 |
| Recurring Patterns |
18 (all of them) |
| New Patterns |
1 (maintenance-startcleanup-bare-slog) |
| Review Date |
2026-06-17 |
🐹 Go Backend
HTTP Status Code Inconsistency (1 issue)
| File |
Location |
Issue |
Suggestion |
handler/magiclink.go |
~L130, ~L139 |
VerifyMagicLink returns HTTP 401 for an invalid/expired token |
Return HTTP 400 to match ResetPassword and VerifyEmail — or change all three to 401. Pick one. |
25th consecutive run (☠️ ASCENDED TO MYTH ☠️): MagicLinkHandler.VerifyMagicLink returns 401 for an expired token; PasswordResetHandler.ResetPassword and EmailVerificationHandler.VerifyEmail return 400 for the same class of error. Clients submitting a bad token to a magic-link endpoint get 401; clients submitting a bad token to a password-reset endpoint get 400. Inconsistent for no reason. Pick a convention and apply it everywhere.
Logging (4 issues)
| File |
Line |
Issue |
Suggestion |
handler/helpers.go |
~L108, L115, L134, L141, L155 |
issueTokens() calls bare slog.ErrorContext instead of accepting a *slog.Logger |
Add logger *slog.Logger param; callers already have h.Logger |
handler/oauth2_common.go |
~L115, L170, L177 |
linkSubjectBestEffort() and createLinkNonce() use bare slog.WarnContext/slog.ErrorContext |
Thread a *slog.Logger parameter through these package-level helpers |
handler/oauth2_common.go |
~L307 |
handleLinkCallback's inner errRedirect closure calls bare slog.ErrorContext |
Pass the logger parameter that handleLinkCallback already receives into the closure |
maintenance/maintenance.go |
~L54, L62 |
StartCleanup uses bare slog.ErrorContext for panic recovery and cleaner errors |
Add an optional *slog.Logger parameter so consuming applications can route cleanup logs through their configured logger |
12th run for issueTokens-bare-slog. 14th run for handlelinkcallback-uses-global-slog. maintenance-startcleanup-bare-slog is new today. The irony: logOrDefault() exists exactly for this pattern, and three of the four files already import it.
Documentation & Comments (5 issues)
| File |
Location |
Issue |
Suggestion |
handler/oauth2.go |
OAuth2UserInfo.Name doc ~L22 |
Says "When empty, Email is used as a fallback" — but findOrCreateUser passes the empty name directly to CreateOIDCUser. No fallback exists. |
Implement the fallback or remove the lie from the doc |
handler/password_reset.go |
Struct comment ~L21-23, RequestReset godoc ~L62-64 |
Both claim "Returns 503 if SendResetEmail is nil" — but Validate() now requires it. This code path is unreachable. |
Remove the 503 mention from both docstrings |
handler/auth.go |
AuthHandler struct ~L33-36 |
CookieName, SecureCookies, DisableSignup, RequireVerification are exported fields with no godoc. Fields above them are documented. |
Add one-line godoc per field |
handler/auth.go + 4 others |
Validate() methods |
All handlers validate Users, JWT, session config — but NOT CookieName. An empty CookieName silently accepts requests and sets nameless cookies. |
Add if h.CookieName == "" { return errors.New("AuthHandler misconfigured: CookieName is required") } to all five handlers |
handler/oidc.go |
OIDCHandler.Callback() ~L200 |
Hardcodes "/?oidc_login=1" redirect. OAuth2Handler has a configurable LoginRedirect field for exactly this. |
Add LoginRedirect string to OIDCHandler with a matching helper |
13th run for oauth2userinfo-name-doc-stale. 7th run for password-reset-handler-stale-503-docstring and auth-handler-undocumented-exported-fields. 11th run for cookiename-not-validated-at-startup. 2nd run for oidc-vs-oauth2-login-redirect-inconsistency.
Code Structure (5 issues)
| File |
Location |
Issue |
Suggestion |
handler/helpers.go |
issueTokens() ~L122-124 |
Inlines TTL fallback instead of using the existing defaultDuration() helper |
ttl = defaultDuration(ttl, DefaultRefreshTokenTTL) — one line |
handler/magiclink.go vs handler/email_verification.go |
TTL computation |
MagicLinkHandler wraps TTL in tokenTTL() method; EmailVerificationHandler inlines defaultDuration() |
Pick one style and apply it consistently |
handler/helpers.go |
deleteUserResource() |
9 parameters including 4 distinct string message args. Every call site passes nearly identical strings. |
Collapse logMessage and internalMessage (identical at every call site) or use a config struct |
handler/auth.go |
Logout() ~L199-213 |
4 levels of nested if for a 15-line function |
Extract tryRevokeSession() helper, flatten to 2 nesting levels |
handler/oidc.go |
NewOIDCHandler() ~L51 |
9 positional parameters, 5 adjacent strings. Easy to silently swap. |
All other handlers use struct config — do the same |
7th run for issueTokens-defaultduration-inconsistency and magiclinkhandler-tokenttl-method-inconsistency. 6th run for deleteUserResource-parameter-overload. 5th run for logout-deep-nesting. 2nd run for newoIDchandler-parameter-overload.
WebAuthn / Passkey (2 issues)
| File |
Location |
Issue |
Suggestion |
handler/passkey.go |
loadWebAuthnCredentials() ~L124-126 |
Logs "skipping corrupted passkey credential" without slog.Any("error", err) — the actual error is silently dropped |
Add , slog.Any("error", err) to the WarnContext call |
handler/passkey.go |
FinishAuthentication() ~L323-348 |
Uses 5 mutable outer-scope variables mutated inside a DiscoverableUserHandler closure |
Extract a resolvePasskeyUser() helper that returns cleanly instead of side-effecting captured variables |
7th run for loadwebauthn-credentials-error-dropped. 11th run for finishauthentication-closure-side-effects. The missing slog.Any("error", err) is literally a copy-paste from the line above it.
🧪 Testing
Critical Test Bug (1 issue)
| File |
Test |
Issue |
Suggestion |
auth/totp_replay_test.go |
TestTOTPUsedCodeCache_WasUsed_expiredEntry |
Inserts entries using string keys ("user1\x00999999") but TOTPUsedCodeCache.entries uses totpCacheKey{userID, code} struct keys. WasUsed loads with struct key, finds nothing, returns false vacuously. The expiry-check branch is never reached. |
Replace c.entries.Store("user1\x00999999", ...) with c.entries.Store(totpCacheKey{userID: "user1", code: "999999"}, ...) |
5th run. This test exists specifically to verify expired entries are NOT reported as used. It does not test that. Anyone relying on it has false confidence in the replay-protection system.
📈 Pattern Analysis
Recurring Themes
| Pattern |
Occurrences Today |
Total Seen |
First Observed |
| token-expiry-check-inconsistency |
1 |
25 |
2026-04-17 |
| handlelinkcallback-uses-global-slog |
1 |
14 |
2026-04-17 |
| oauth2userinfo-name-doc-stale |
1 |
13 |
2026-04-17 |
| issueTokens-bare-slog |
1 |
12 |
2026-04-17 |
| cookiename-not-validated-at-startup |
1 |
11 |
2026-05-01 |
| finishauthentication-closure-side-effects |
1 |
11 |
2026-05-01 |
| issueTokens-defaultduration-inconsistency |
1 |
7 |
2026-06-10 |
| magiclinkhandler-tokenttl-method-inconsistency |
1 |
7 |
2026-06-10 |
| loadwebauthn-credentials-error-dropped |
1 |
7 |
2026-06-11 |
| password-reset-handler-stale-503-docstring |
1 |
7 |
2026-06-11 |
| auth-handler-undocumented-exported-fields |
1 |
7 |
2026-06-11 |
| deleteUserResource-parameter-overload |
1 |
6 |
2026-06-12 |
| totp-replay-test-wrong-key-type |
1 |
5 |
2026-06-13 |
| logout-deep-nesting |
1 |
5 |
2026-06-13 |
| oidc-vs-oauth2-login-redirect-inconsistency |
1 |
2 |
2026-06-16 |
| newoIDchandler-parameter-overload |
1 |
2 |
2026-06-16 |
| maintenance-startcleanup-bare-slog |
1 |
1 (new) |
2026-06-17 |
New Pattern
maintenance-startcleanup-bare-slog: StartCleanup in maintenance/maintenance.go accepts no *slog.Logger and uses slog.ErrorContext directly for both panic recovery and cleaner error reporting. Consuming applications with custom slog handlers (JSON, structured, with trace IDs) will see cleanup failures bypass that configuration entirely. Fix: add an optional logger *slog.Logger parameter.
✅ Positive Highlights
- ✅ Consistent
logOrDefault() usage across all handler methods — bare slog is confined to helpers and infrastructure, not handlers themselves
- ✅
listUserResources and deleteUserResource generics are used consistently by all three resource handlers — good deduplication
- ✅ No
sql.ErrNoRows leaking into handler layer — the sentinel pattern has held since the April fix
- ✅
validateSessionConfig helper enforces RefreshCookieName is non-empty when Sessions is set — consistently called from all Validate() methods
💡 Recommendations
For immediate attention (≤5 minutes each):
auth/totp_replay_test.go — Replace string keys with totpCacheKey{...} struct keys in tests that bypass MarkUsed. One-line fix per test. Security-adjacent feature.
handler/passkey.go L126 — Add , slog.Any("error", err) to the WarnContext call. 22 characters.
handler/helpers.go L122-124 — Replace 3-line TTL block with ttl = defaultDuration(ttl, DefaultRefreshTokenTTL).
handler/password_reset.go — Remove the 503 text from struct comment and godoc. Delete two sentences.
Longer-term improvements:
- Thread
*slog.Logger into issueTokens(), createLinkNonce(), linkSubjectBestEffort(), handleLinkCallback(), and maintenance.StartCleanup()
- Add
CookieName != "" checks to all five handler Validate() methods
- Resolve
token-expiry-check-inconsistency — 25 reviews. Choose 400 or 401 for expired tokens and apply it everywhere.
Daily grumpy review for amalgamated-tools/goauth · Run §27716698194
Generated by Daily Grumpy Reviewer · ● 3.8M · ◷
19 grumpies found today. Every single one of them is a recurring pattern — nothing has been fixed since yesterday. The champion,
token-expiry-check-inconsistency, has now appeared in 25 consecutive daily reviews. I'm no longer grumpy; I'm resigned. One new pattern added:maintenance-startcleanup-bare-slog.📋 Full Grumpy Report
Review Overview
maintenance-startcleanup-bare-slog)🐹 Go Backend
HTTP Status Code Inconsistency (1 issue)
handler/magiclink.goVerifyMagicLinkreturns HTTP 401 for an invalid/expired tokenResetPasswordandVerifyEmail— or change all three to 401. Pick one.Logging (4 issues)
handler/helpers.goissueTokens()calls bareslog.ErrorContextinstead of accepting a*slog.Loggerlogger *slog.Loggerparam; callers already haveh.Loggerhandler/oauth2_common.golinkSubjectBestEffort()andcreateLinkNonce()use bareslog.WarnContext/slog.ErrorContext*slog.Loggerparameter through these package-level helpershandler/oauth2_common.gohandleLinkCallback's innererrRedirectclosure calls bareslog.ErrorContextloggerparameter thathandleLinkCallbackalready receives into the closuremaintenance/maintenance.goStartCleanupuses bareslog.ErrorContextfor panic recovery and cleaner errors*slog.Loggerparameter so consuming applications can route cleanup logs through their configured loggerDocumentation & Comments (5 issues)
handler/oauth2.goOAuth2UserInfo.Namedoc ~L22findOrCreateUserpasses the empty name directly toCreateOIDCUser. No fallback exists.handler/password_reset.goRequestResetgodoc ~L62-64SendResetEmailis nil" — butValidate()now requires it. This code path is unreachable.handler/auth.goAuthHandlerstruct ~L33-36CookieName,SecureCookies,DisableSignup,RequireVerificationare exported fields with no godoc. Fields above them are documented.handler/auth.go+ 4 othersValidate()methodsUsers,JWT, session config — but NOTCookieName. An emptyCookieNamesilently accepts requests and sets nameless cookies.if h.CookieName == "" { return errors.New("AuthHandler misconfigured: CookieName is required") }to all five handlershandler/oidc.goOIDCHandler.Callback()~L200"/?oidc_login=1"redirect.OAuth2Handlerhas a configurableLoginRedirectfield for exactly this.LoginRedirect stringtoOIDCHandlerwith a matching helperCode Structure (5 issues)
handler/helpers.goissueTokens()~L122-124defaultDuration()helperttl = defaultDuration(ttl, DefaultRefreshTokenTTL)— one linehandler/magiclink.govshandler/email_verification.goMagicLinkHandlerwraps TTL intokenTTL()method;EmailVerificationHandlerinlinesdefaultDuration()handler/helpers.godeleteUserResource()logMessageandinternalMessage(identical at every call site) or use a config structhandler/auth.goLogout()~L199-213iffor a 15-line functiontryRevokeSession()helper, flatten to 2 nesting levelshandler/oidc.goNewOIDCHandler()~L51WebAuthn / Passkey (2 issues)
handler/passkey.goloadWebAuthnCredentials()~L124-126slog.Any("error", err)— the actual error is silently dropped, slog.Any("error", err)to the WarnContext callhandler/passkey.goFinishAuthentication()~L323-348DiscoverableUserHandlerclosureresolvePasskeyUser()helper that returns cleanly instead of side-effecting captured variables🧪 Testing
Critical Test Bug (1 issue)
auth/totp_replay_test.goTestTOTPUsedCodeCache_WasUsed_expiredEntry"user1\x00999999") butTOTPUsedCodeCache.entriesusestotpCacheKey{userID, code}struct keys.WasUsedloads with struct key, finds nothing, returnsfalsevacuously. The expiry-check branch is never reached.c.entries.Store("user1\x00999999", ...)withc.entries.Store(totpCacheKey{userID: "user1", code: "999999"}, ...)📈 Pattern Analysis
Recurring Themes
New Pattern
maintenance-startcleanup-bare-slog:StartCleanupinmaintenance/maintenance.goaccepts no*slog.Loggerand usesslog.ErrorContextdirectly for both panic recovery and cleaner error reporting. Consuming applications with custom slog handlers (JSON, structured, with trace IDs) will see cleanup failures bypass that configuration entirely. Fix: add an optionallogger *slog.Loggerparameter.✅ Positive Highlights
logOrDefault()usage across all handler methods — bareslogis confined to helpers and infrastructure, not handlers themselveslistUserResourcesanddeleteUserResourcegenerics are used consistently by all three resource handlers — good deduplicationsql.ErrNoRowsleaking into handler layer — the sentinel pattern has held since the April fixvalidateSessionConfighelper enforcesRefreshCookieNameis non-empty when Sessions is set — consistently called from allValidate()methods💡 Recommendations
For immediate attention (≤5 minutes each):
auth/totp_replay_test.go— Replace string keys withtotpCacheKey{...}struct keys in tests that bypassMarkUsed. One-line fix per test. Security-adjacent feature.handler/passkey.goL126 — Add, slog.Any("error", err)to the WarnContext call. 22 characters.handler/helpers.goL122-124 — Replace 3-line TTL block withttl = defaultDuration(ttl, DefaultRefreshTokenTTL).handler/password_reset.go— Remove the 503 text from struct comment and godoc. Delete two sentences.Longer-term improvements:
*slog.LoggerintoissueTokens(),createLinkNonce(),linkSubjectBestEffort(),handleLinkCallback(), andmaintenance.StartCleanup()CookieName != ""checks to all five handlerValidate()methodstoken-expiry-check-inconsistency— 25 reviews. Choose 400 or 401 for expired tokens and apply it everywhere.Daily grumpy review for amalgamated-tools/goauth · Run §27716698194