Skip to content

nightly: 1 production fix (2026-06-14) - #294

Closed
njrini99-code wants to merge 2 commits into
mainfrom
claude/awesome-babbage-cbt5ds
Closed

nightly: 1 production fix (2026-06-14)#294
njrini99-code wants to merge 2 commits into
mainfrom
claude/awesome-babbage-cbt5ds

Conversation

@njrini99-code

@njrini99-code njrini99-code commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Daily brief — 2026-06-14

24h totals

  • Total errors: 100+ (truncated by Vercel page cap; extrapolated ≈576/day)
  • Distinct patterns: 1 (one root cause accounts for 100% of error volume)
  • Affected users: indeterminable from runtime log payload (no user_id surfaced) — see "Couldn't fully verify" below
  • Time window: 2026-06-13T04:02 → 2026-06-14T04:02 UTC
  • Source coverage: Vercel runtime logs + Vercel deployments only. Supabase MCP was unreachable from this environment (Host not in allowlist: mcp.supabase.com) — admin_events, error_logs, and pg_stat_statements were not queried.

Top patterns (by occurrences × severity)

# Occurrences Route / method Status Pattern Root-cause hypothesis
1 100+ (page cap) POST /golf/admin 500 Error: Forbidden thrown from two server actions (getAdminDashboardData, getAdminDashboardRollup) The admin dashboard runs a 5-min visibility-aware refresh polling both server actions in parallel; each throws a bare Error('Forbidden') on the role check, returning a 500 per request (×2 per tick). The SSR layout guard catches a fresh navigation, but a tab kept open across a session/role change still polls — and the inner rollup helper re-throws with a rollupA failed: Forbidden wrapper that the client's message === 'Forbidden' guard missed entirely, so a real admin hitting an internal RLS denial would never freeze the timer. Result: one tab → ~576 runtime errors/day.

Performance

  • Supabase MCP unavailable → pg_stat_statements not pulled. No slow-query report this run.

Deployment health

What I couldn't fully verify

  • Supabase tables (admin_events, error_logs, pg_stat_statements) — MCP host blocked by env network policy. Run separately to confirm there isn't a second pattern hiding only in error_logs (the app's logServerError() sink writes there, not to Vercel).
  • Affected-user count — the Vercel runtime log row format truncates payload after ~30 chars, hiding the user_id. The polling cadence (consistent 5-min ticks across 2h of contiguous traffic) implies a single tab; one or two users at most.

Fixes applied

1. /golf/admin Forbidden flood (576+ errors/day → expected 0)

Pattern: POST /golf/admin → 500 → Error: Forbidden at U/at ah
Files touched:

  • src/app/golf/actions/admin-data.ts — new checkAdminAccess() server action (non-throwing access probe).
  • src/app/golf/admin/page.tsxloadData now calls checkAdminAccess() first and short-circuits cleanly when access drops; error-guard regex widened to /\b(Unauthorized|Forbidden)\b/ so wrapped messages (rollupA failed: Forbidden) also freeze polling.

Diagnosis: Two failure modes fed the flood:

  1. A dashboard tab kept open through a session/role change keeps polling every 5 min. The SSR layout's redirect only fires on a fresh navigation; the data actions then 500 on every tick (×2 — one per parallel action).
  2. The client guard meant to stop polling matched with message === 'Forbidden' (strict equality). The inner rollup helper re-throws with a rollupA failed: prefix, slipping past the guard — a real admin hitting an internal RLS denial would poll forever.

Approach: Added a non-throwing checkAdminAccess() that returns { allowed, reason }. The page calls it before the heavy data actions; on { allowed: false } the catch path trips sessionExpired=true, which feeds null to useVisibilityAwareInterval and tears the timer down. No 500. Existing throws stay as backstops on the actions themselves. Guard regex widened to match wrapped messages with the same intent.

Not fixed (and why):

  • Single-occurrence transients — none in 24h. Skipped per instructions.
  • Cron failures — the only /api/cron/* log entry was an informational warning from the coachhelm-roster-sweep ([insights.triggerPlayerInsi…) with status 200. Not actionable.
  • Deployment failures — none.

DO NOT auto-merge — leaving for human review.


Generated by Claude Code

Greptile Summary

This PR fixes a 576+ errors/day flood on POST /golf/admin caused by the admin dashboard's 5-minute visibility-aware polling continuing to call auth-gating server actions after a tab's session or role had dropped. A second bug let wrapped error messages like rollupA failed: Forbidden slip past the strict equality guard, keeping the timer alive after a real RLS denial.

  • Adds a non-throwing checkAdminAccess() server action that returns { allowed, reason } — the page calls it first on each poll tick, short-circuits on { allowed: false }, and never reaches the 500-throwing data actions. Transient DB errors are re-thrown (not collapsed to forbidden) so a Supabase hiccup leaves the timer running.
  • Widens the client-side auth-guard from === 'Forbidden' to /\b(Unauthorized|Forbidden)\b/ so wrapped messages from inner rollup helpers also stop the timer.

Confidence Score: 5/5

The change is safe to merge — it eliminates a concrete 576+/day 500 flood by gating polls behind a non-throwing access probe and correctly preserves the polling timer on transient DB errors.

Both changes are tightly scoped: the new server action returns a plain object rather than throwing, the DB-error re-throw keeps the timer alive during Supabase hiccups, and the regex widening only catches messages that already semantically mean auth denial. No new error paths are introduced and the prior review concern about DB errors collapsing to forbidden has been addressed correctly.

No files require special attention.

Important Files Changed

Filename Overview
src/app/golf/actions/admin-data.ts Adds checkAdminAccess() — a non-throwing probe that returns { allowed, reason } instead of 500ing; correctly re-throws transient DB errors to preserve timer liveness.
src/app/golf/admin/page.tsx Gates loadData on checkAdminAccess() before the parallel data fetches; widens the session-expiry regex to catch wrapped error messages like 'rollupA failed: Forbidden'.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser Tab
    participant SA_Check as checkAdminAccess() [server]
    participant SA_Data as getAdminDashboardData() [server]
    participant SA_Rollup as getAdminDashboardRollup() [server]
    participant DB as Supabase DB

    Note over Browser: Every 5 min (visibility-aware)
    Browser->>SA_Check: call (access probe)
    SA_Check->>DB: auth.getUser()
    SA_Check->>DB: "SELECT role FROM users WHERE id=?"
    alt "user not found or role != admin"
        SA_Check-->>Browser: allowed: false
        Browser->>Browser: throw Error Unauthorized or Forbidden
        Browser->>Browser: setSessionExpired true
        Browser->>Browser: timer torn down, no more polls, no 500s
    else transient DB error
        SA_Check-->>Browser: throws DB error
        Browser->>Browser: setError message, timer stays alive
    else allowed true
        SA_Check-->>Browser: allowed: true
        Browser->>SA_Data: call
        Browser->>SA_Rollup: call with catch null
        SA_Data-->>Browser: data
        SA_Rollup-->>Browser: rollup or null
        Browser->>Browser: setData setRollup setLastRefresh
    end
Loading

Reviews (2): Last reviewed commit: "fix(admin): don't collapse transient DB ..." | Re-trigger Greptile

…rors/day)

100% of production runtime errors in the last 24h were the same pattern:
POST /golf/admin 500 "Error: Forbidden" firing every ~5 minutes, two per
tick (one each from getAdminDashboardData and getAdminDashboardRollup —
the dashboard's visibility-aware refresh polls both in parallel). Both
actions throw bare `Error('Forbidden')` on the role check, which surfaces
as a Vercel runtime error and floods the runtime log faster than real
issues can show up.

Two failure paths fed this:

1. A tab kept open through a session/role change still polls every 5 min.
   The SSR layout's redirect only fires on a fresh navigation; the data
   actions then 500 on every subsequent tick.
2. The client guard that's supposed to freeze polling matched the error
   with exact equality (`message === 'Forbidden'`). The inner rollup
   helper re-throws with a `rollupA failed: Forbidden` prefix, so a real
   admin hitting an internal RLS denial would slip past the guard and
   poll forever.

Fix is two small, targeted changes:

- New `checkAdminAccess()` server action: non-throwing access probe.
  Returns `{ allowed, reason }` so the auth-fail path no longer 500s on
  its own — keeps prod logs clean. `loadData` now gates on this before
  the heavy data actions; on `{ allowed: false }` it short-circuits and
  trips the session-expired path, tearing down the polling interval.
- Harden the catch-block guard to match wrapped messages via
  `/\b(Unauthorized|Forbidden)\b/`. Restores the freeze-on-denial
  behaviour for the wrapped rollup case.

Existing server-side throws stay as defense-in-depth. SSR layout guard
is unchanged.
@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
helmv3 Ready Ready Preview, Comment Jun 14, 2026 4:24am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@njrini99-code, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 6 minutes and 39 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 994bd067-e32e-4dd1-9ea8-a0c1c9e3a64d

📥 Commits

Reviewing files that changed from the base of the PR and between 0984669 and 2ada0df.

📒 Files selected for processing (2)
  • src/app/golf/actions/admin-data.ts
  • src/app/golf/admin/page.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/awesome-babbage-cbt5ds

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread src/app/golf/actions/admin-data.ts Outdated
…on expiry

Greptile P1 on PR #294: checkAdminAccess() was treating any error from the
`users` role lookup the same as `role !== 'admin'` — both returned
{ allowed: false, reason: 'forbidden' }, which the client maps to
`throw new Error('Forbidden')`, hits the /\bForbidden\b/ regex, and
permanently sets sessionExpired=true. A real admin whose role query hit
a transient Supabase hiccup would see "Session Expired" and have to
reload, even though their session is fine.

Split the cases: re-throw the raw userErr so the generic catch path
surfaces it as a retriable error (timer keeps running), and only return
`forbidden` when the role is actually wrong.
@njrini99-code

Copy link
Copy Markdown
Owner Author

Folded into #304 (feat/coachhelm-stats-roundup) — merged clean, combined gates green. Branch intact + reopenable.

@njrini99-code
njrini99-code deleted the claude/awesome-babbage-cbt5ds branch June 29, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants