Skip to content

fix: error-overview sweep — TDZ import cycle, expired-session UX, Sentry double-reports, miner deadlock retry#803

Merged
njrini99-code merged 3 commits into
mainfrom
fix/error-overview-0710
Jul 11, 2026
Merged

fix: error-overview sweep — TDZ import cycle, expired-session UX, Sentry double-reports, miner deadlock retry#803
njrini99-code merged 3 commits into
mainfrom
fix/error-overview-0710

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Error overview (7-day window, all three channels)

Surveyed Vercel runtime errors, in-app admin_events, and Sentry. Every distinct error root-caused; four code fixes here, the rest verified as already resolved by the deploy earlier today.

Fixes in this PR

1. CoachHelm cold-start TDZ crash (generateTeamInsight.generateAlerts failed: Cannot access 'a' before initialization — Jun 23 + Jul 10)
Forensic trace found a genuine value-level import cycle: insights.ts statically imports __registerTriggerPlayerInsightsAfterRound from trigger-insights-bridge.ts, and the bridge lazily await import()ed insights.ts back. On a cold Fluid Compute instance, a background after() round trigger racing a dashboard server action could observe insights.ts mid-evaluation → TDZ. The bridge's dynamic import is deleted; both real callers (post-round-trigger.ts, roster-sweep cron) now carry a synchronous side-effect import '@/app/golf/actions/insights' so registration completes at module-init with no async gap. Security property unchanged: the impl is still never exported from a 'use server' file. (Madge missed this cycle previously because it was run without --ts-config, dropping all @/ alias edges.)

2. Golf dashboard Not authenticated digests (×2, Jul 6)
Session passes the top-of-page check, expires before the data fetch re-validates. That narrow case now redirects to /golf/login?returnTo=… in both coach and player branches; anything else still surfaces to the route error boundary (P002/P426 — real outages must never be swallowed).

3. Sentry double-reports of expected baseball control-flow throws (BaseballUnauthorizedError: You must be signed in — a logged-out tab's 60s NotificationBell poll)
withBaseballAction already classifies these as handled/expected (admin_events + skipSentry) and re-raises for caller branching — but the re-raise escapes the server-action boundary where onRequestError/console capture re-report it as an unhandled Sentry Error. The six expected classes join sharedIgnoreErrors. They stay fully visible in admin_events / Helm Bridge; only the duplicate Sentry Error goes away. The client poll already degrades gracefully — no UX change needed.

4. pattern-miner supersede deadlock (40P01, Jul 8 cron)
Roster-sweep batches racing a round-submit mine over the same team deadlocked on the multi-row supersede UPDATE. The operation is idempotent (deterministic ids, converges on re-run), so it now retries up to 2× with backoff; only a persistent failure logs.

Verified already-resolved (no code needed)

Gates

tsc clean · eslint --max-warnings 0 clean · 494 test files / 4,917 tests green · production build green. Test blast radius of the side-effect import covered: post-round-trigger.test.ts stubs insights.ts (bridge is fully mocked there); the other three affected suites already mock post-round-trigger wholesale.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX

…try double-reports, miner deadlock retry

Fixes for every live error across Vercel runtime, admin_events, and Sentry (7d window):

- CoachHelm cold-start TDZ ("Cannot access 'a' before initialization" in
  generateAlerts): trigger-insights-bridge lazily import()ed insights.ts,
  the dynamic back-edge of a value-level import cycle (insights.ts
  statically imports the bridge's register fn). Two concurrent first-touches
  on a cold instance — a background after() round trigger racing a dashboard
  action — could observe insights.ts mid-evaluation. The bridge no longer
  imports insights.ts; both real callers (post-round-trigger, roster-sweep
  cron) now carry a synchronous side-effect import so registration completes
  at module init with no async gap.

- Golf dashboard "Not authenticated" digests: a session can pass the
  top-of-page check yet expire before the data fetch re-validates it. That
  narrow case now redirects to /golf/login (both coach and player branches)
  instead of crashing to the route error boundary; real outages still surface.

- Sentry duplicate captures of expected baseball control-flow throws
  (observed as BaseballUnauthorizedError from a logged-out tab's 60s
  NotificationBell poll): withBaseballAction already classifies these as
  handled/expected and re-raises; the re-raise escapes the server-action
  boundary where onRequestError/console capture re-reports it as an
  unhandled Error. The six expected error classes are now in
  sharedIgnoreErrors — they remain fully visible in admin_events/Bridge.

- pattern-miner supersede deadlock (40P01, roster-sweep cron racing a
  round-submit mine over the same team): the supersede UPDATE is idempotent,
  so retry up to 2x with backoff instead of surfacing a transient as an error.

Verified: tsc clean, eslint --max-warnings 0 clean, 494 test files /
4,917 tests green, production build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX
@supabase

supabase Bot commented Jul 11, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Preview Jul 11, 2026 3:23pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aaa96e0e-b50d-4534-a6a3-5b12c1315e3b

📥 Commits

Reviewing files that changed from the base of the PR and between 3cb3b41 and 0e0e7bb.

📒 Files selected for processing (3)
  • memory/features/auth-onboarding-join.md
  • src/app/golf/actions/dashboard-data.ts
  • src/lib/coachhelm/v2/mining/pattern-miner.ts

Summary by CodeRabbit

  • Bug Fixes
    • Expired sessions on the golf dashboard now redirect to the login page while preserving the return path.
    • Temporary authentication service failures are handled as errors instead of being mistaken for logged-out sessions.
    • Improved reliability when processing concurrent coaching insights and roster updates, reducing failures caused by timing conflicts or database contention.
  • Documentation
    • Updated dashboard authentication behavior documentation to reflect session-expiration handling.

Walkthrough

The PR adds targeted dashboard redirects for expired sessions, eager insight-trigger registration, deadlock retries for stale-pattern updates, and Sentry filtering for expected application errors.

Changes

Dashboard authentication handling

Layer / File(s) Summary
Expired-session redirect and auth error flow
src/app/golf/(dashboard)/dashboard/page.tsx:28-41,143,218, src/app/golf/actions/dashboard-data.ts:250-258,766-771, memory/features/auth-onboarding-join.md:75
Dashboard fetches redirect "Not authenticated" failures to /golf/login?returnTo=/golf/dashboard, while retryable or 5xx auth failures remain error-boundary failures; the rule is documented.

Insight trigger registration

Layer / File(s) Summary
Synchronous bridge registration contract
src/lib/coachhelm/v2/trigger-insights-bridge.ts:36-50,102-106
The bridge no longer dynamically imports insights.ts; missing registration now throws an explicit side-effect-import error.
Caller wiring and test isolation
src/app/api/cron/coachhelm-roster-sweep/route.ts:17-21, src/lib/coachhelm/v2/post-round-trigger.ts:20-25, src/test/coachhelm/v2/post-round-trigger.test.ts:12-17
Cron and post-round modules eagerly load insight registration, while the test mocks the module to suppress initialization effects.

Runtime error resilience

Layer / File(s) Summary
Deadlock retry and expected-error filtering
src/lib/coachhelm/v2/mining/pattern-miner.ts:995-1019, src/instrumentation.ts:21-35
Pattern superseding retries PostgreSQL deadlocks up to three times with backoff and jitter, while shared Sentry filtering ignores named expected application errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • njrini99-code/helmv3#798: Updates the same post-round trigger and insights bridge modules with engine-failure classification and result codes.

Suggested labels: security

🚥 Pre-merge checks | ✅ 11 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is related to the PR, but it is missing a required Conventional Commits scope. Use a Conventional Commit title with one of the required scopes, e.g. fix(coachhelm): ... or another matching scope.
✅ Passed checks (11 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the four code changes and their motivations, so it is clearly on-topic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No Service-Role In Client Bundles ✅ Passed PASS: Touched diffs add only side-effect imports/error handling; no new SUPABASE_SERVICE_ROLE_KEY refs or service-role client creation. page.tsx:109 is bare createClient() via server.ts:9-23.
Rls Coverage On New Tables ✅ Passed No migration files were changed in the PR diff; only src/app, src/lib, and test files were modified.
Auth Check In Server Actions ✅ Passed No files changed in this PR match src/app/**/actions/**/*.ts, so the auth-gating check is not applicable.
Sport-Prefixed Table Names ✅ Passed No unprefixed table names in changed TS/TSX files; queries use golf_* only (route.ts:57,98; page.tsx:196; pattern-miner.ts:247,922,1004; post-round-trigger.ts:112).
No Destructive Writes ✅ Passed Changed save/submit code uses upsert/update only; no DELETE→INSERT on the same table in any changed file. See pattern-miner.ts:963-1006 and post-round-trigger.ts:101-115.
No Edits To Historical Migrations ✅ Passed Diff touches only src/* files; no supabase/migrations/ paths changed, so the historical-migration rule is satisfied.
Conventional Commits ✅ Passed HEAD squash subject fix: error-overview sweep — TDZ import cycle, expired-session UX, Sentry double-reports, miner deadlock retry matches the required Conventional Commits pattern.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/error-overview-0710
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)

ast-grep could not parse rule config: /ast-grep-rules/../git/.coderabbit/ast-grep/no-explicit-any.yml

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/app/golf/actions/dashboard-data.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/lib/coachhelm/v2/mining/pattern-miner.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix cold-start TDZ crash, expired-session redirects, Sentry noise, and miner deadlock retries

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Remove async import back-edge causing cold-start TDZ crashes in CoachHelm insights trigger.
• Redirect narrowly-expired golf dashboard sessions to login instead of error-boundary digests.
• Suppress duplicate Sentry issues for expected baseball auth/control-flow errors.
• Retry Postgres deadlock victims during pattern supersede UPDATE to avoid transient cron failures.
Diagram

graph TD
  A["CoachHelm callers\n(post-round + cron)"] -->|"side-effect import"| B["insights actions\nmodule init"] -->|"register impl"| C["trigger-insights\nbridge"] -->|"delegate"| B
  A -->|"call trigger"| C
  D["Golf dashboard\npage"] -->|"Not authenticated"| E["redirect\n/login?returnTo"]
  F["Pattern miner\nsupersede"] -->|"UPDATE w/ retry"| G[("Postgres")]
  H["Sentry\ninstrumentation"] -->|"ignoreErrors"| I{{"Sentry issue\ningestion"}}
  subgraph Legend
    direction LR
    _mod["Module/Route"] ~~~ _db[("Database")] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Extract registration + impl into a dedicated server-only module
  • ➕ Eliminates side-effect imports from callers; bridge can import directly without cycles
  • ➕ Makes initialization explicit and easier to test/migrate
  • ➕ Reduces coupling to a large 'use server' file
  • ➖ Likely forces moving many module-private helpers out of insights.ts (high churn/risk)
  • ➖ May inadvertently widen the server-action export surface if not carefully isolated
2. Explicit init function called by entrypoints (instead of module-scope side effects)
  • ➕ More explicit than side-effect imports; failures can be surfaced early
  • ➕ Can be instrumented/guarded (exactly-once)
  • ➖ Still requires every entrypoint to remember to call init
  • ➖ Doesn’t inherently prevent import cycles; just changes their manifestation
3. Pass the trigger implementation via dependency injection
  • ➕ Bridge becomes a thin adapter; no global mutable registration state
  • ➕ Improves unit test ergonomics (no module-mocking required)
  • ➖ Large signature/threading changes across call sites
  • ➖ Harder to retrofit into existing Next/server-action boundaries

Recommendation: Keep the PR’s approach: moving the dynamic import out of the bridge and requiring synchronous module-init registration via side-effect imports in the two legitimate callers is the lowest-churn way to eliminate the TDZ race without expanding the 'use server' export surface. Consider the dedicated server-only module extraction only if insights.ts is ever meaningfully split/refactored, since it currently depends on a large cluster of module-private helpers.

Files changed (7) +85 / -22

Bug fix (6) +79 / -22
route.tsPreload insights registration for cron-triggered CoachHelm runs +5/-0

Preload insights registration for cron-triggered CoachHelm runs

• Adds a side-effect import of the insights actions module so the trigger implementation registers synchronously at module init. Avoids relying on the bridge’s former lazy dynamic import that contributed to a cold-start TDZ import cycle.

src/app/api/cron/coachhelm-roster-sweep/route.ts

page.tsxRedirect expired sessions during dashboard data fetches +16/-2

Redirect expired sessions during dashboard data fetches

• Introduces a narrow handler that detects the specific 'Not authenticated' error and redirects to /golf/login with a returnTo back to the dashboard. Applies this handling to both coach and player Promise.all fetch paths while still surfacing real outages to the route error boundary.

src/app/golf/(dashboard)/dashboard/page.tsx

instrumentation.tsSuppress duplicate Sentry issues for expected baseball control-flow errors +15/-0

Suppress duplicate Sentry issues for expected baseball control-flow errors

• Extends sharedIgnoreErrors with expected baseball/auth control-flow error class names to prevent Next’s request-level error capture from re-reporting handled exceptions. Preserves visibility via existing admin_events/Helm Bridge paths while removing duplicate Sentry issue noise.

src/instrumentation.ts

pattern-miner.tsRetry supersede UPDATE on Postgres deadlock (40P01) +20/-8

Retry supersede UPDATE on Postgres deadlock (40P01)

• Wraps the multi-row supersede UPDATE in a small retry loop with linear backoff when Postgres reports a deadlock victim. Leverages the operation’s idempotence to convert transient deadlocks into successful retries, only logging persistent failures.

src/lib/coachhelm/v2/mining/pattern-miner.ts

post-round-trigger.tsEnsure insights registration completes before using the trigger bridge +6/-0

Ensure insights registration completes before using the trigger bridge

• Adds a side-effect import of the insights actions module at top-level to guarantee bridge registration completes synchronously before any trigger invocation. This removes the async gap that could allow a cold-start TDZ race between concurrent first touches.

src/lib/coachhelm/v2/post-round-trigger.ts

trigger-insights-bridge.tsRemove lazy dynamic import to break TDZ-causing import cycle +17/-12

Remove lazy dynamic import to break TDZ-causing import cycle

• Updates bridge documentation and behavior to require callers to preload insights.ts for registration, and removes the prior await import() path. The bridge now strictly delegates to a registered implementation and throws a clearer error if registration is missing.

src/lib/coachhelm/v2/trigger-insights-bridge.ts

Tests (1) +6 / -0
post-round-trigger.test.tsStub insights module to accommodate new side-effect import +6/-0

Stub insights module to accommodate new side-effect import

• Mocks the large 'use server' insights module so post-round-trigger tests remain isolated after introducing a side-effect import. Keeps the bridge fully mocked and avoids loading the real registration side effects during unit tests.

src/test/coachhelm/v2/post-round-trigger.test.ts

@greptile-apps

greptile-apps Bot commented Jul 11, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes several observed production error paths across GolfHelm, CoachHelm, and BaseballHelm. The main changes are:

  • Registers CoachHelm insight generation through synchronous side-effect imports.
  • Redirects expired Golf dashboard sessions back to login.
  • Suppresses duplicate Sentry reports for expected Baseball control-flow errors.
  • Retries CoachHelm pattern supersede updates after Postgres deadlocks.
  • Updates the post-round trigger test mock for the new side-effect import.

Confidence Score: 5/5

This looks safe to merge after considering the miner retry cleanup.

  • The bridge registration callers match the new synchronous contract.
  • The dashboard redirect follows the existing thrown auth error path.
  • The miner retry can still leave stale active patterns after an unrecovered supersede failure, but the data converges on a later successful mine.

src/lib/coachhelm/v2/mining/pattern-miner.ts

Important Files Changed

Filename Overview
src/app/api/cron/coachhelm-roster-sweep/route.ts Adds the CoachHelm insights side-effect import before the cron route calls the bridge.
src/app/golf/(dashboard)/dashboard/page.tsx Adds a narrow expired-session redirect around coach and player dashboard data fetches.
src/instrumentation.ts Adds expected Baseball control-flow error classes to shared Sentry ignore handling.
src/lib/coachhelm/v2/mining/pattern-miner.ts Adds a deadlock retry around stale-pattern supersede, with remaining transient consistency risks if supersede still fails.
src/lib/coachhelm/v2/post-round-trigger.ts Adds the CoachHelm insights side-effect import before post-round trigger code uses the bridge.
src/lib/coachhelm/v2/trigger-insights-bridge.ts Removes the lazy dynamic import and makes registration a synchronous caller-side contract.
src/test/coachhelm/v2/post-round-trigger.test.ts Mocks the newly imported insights action module for the post-round trigger test.

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/lib/coachhelm/v2/mining/pattern-miner.ts:1004-1012
**Supersede Failure Leaves Stale Patterns**

When the new retry loop exhausts or sees any non-`40P01` error, `savePatterns` only logs and returns after the earlier upserts have already committed. That leaves both the newly upserted active patterns and the old patterns that this update was supposed to retire visible until a later mine happens to clean them up.

### Issue 2 of 2
src/lib/coachhelm/v2/mining/pattern-miner.ts:1013
**Deadlock Retry Depends On Code Shape**

This only retries when the returned error has `code === '40P01'`. If the Supabase/PostgREST layer surfaces the observed deadlock as a thrown exception or a wrapped error without that exact field, the loop breaks after the first attempt and the live deadlock path still logs a failed supersede without retrying.

Reviews (1): Last reviewed commit: "fix: error-overview sweep — TDZ import c..." | Re-trigger Greptile

Comment on lines +1004 to +1012
const { error } = await fromUntyped(supabase, 'golf_patterns_v2')
.update({ is_active: false, updated_at: new Date().toISOString() })
.in('player_id', playerIds)
.eq('is_active', true)
.not('id', 'in', idList)
// Keep coach-curated patterns visible; only retire auto-detected ones
// (NULL or non-preserved lifecycle_state).
.or('lifecycle_state.is.null,lifecycle_state.not.in.(confirmed,addressed,resolved,dismissed)');
supersedeError = error;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Supersede Failure Leaves Stale Patterns

When the new retry loop exhausts or sees any non-40P01 error, savePatterns only logs and returns after the earlier upserts have already committed. That leaves both the newly upserted active patterns and the old patterns that this update was supposed to retire visible until a later mine happens to clean them up.

Rule Used: No DELETE-then-INSERT in any save/submit/sync path... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/coachhelm/v2/mining/pattern-miner.ts
Line: 1004-1012

Comment:
**Supersede Failure Leaves Stale Patterns**

When the new retry loop exhausts or sees any non-`40P01` error, `savePatterns` only logs and returns after the earlier upserts have already committed. That leaves both the newly upserted active patterns and the old patterns that this update was supposed to retire visible until a later mine happens to clean them up.

**Rule Used:** No DELETE-then-INSERT in any save/submit/sync path... ([source](.greptile))

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

// (NULL or non-preserved lifecycle_state).
.or('lifecycle_state.is.null,lifecycle_state.not.in.(confirmed,addressed,resolved,dismissed)');
supersedeError = error;
if (!error || error.code !== '40P01') break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Deadlock Retry Depends On Code Shape

This only retries when the returned error has code === '40P01'. If the Supabase/PostgREST layer surfaces the observed deadlock as a thrown exception or a wrapped error without that exact field, the loop breaks after the first attempt and the live deadlock path still logs a failed supersede without retrying.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/coachhelm/v2/mining/pattern-miner.ts
Line: 1013

Comment:
**Deadlock Retry Depends On Code Shape**

This only retries when the returned error has `code === '40P01'`. If the Supabase/PostgREST layer surfaces the observed deadlock as a thrown exception or a wrapped error without that exact field, the loop breaks after the first attempt and the live deadlock path still logs a failed supersede without retrying.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

@qodo-code-review

qodo-code-review Bot commented Jul 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 98 rules

Grey Divider


Remediation recommended

1. Dashboard auth redirect undocumented ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The golf dashboard now redirects to /golf/login?returnTo=/golf/dashboard when a session expires
mid-request, which changes user-visible behavior. No corresponding update (or explicit in-code note
that docs remain accurate) was made to the relevant memory/features/* documentation.
Code

src/app/golf/(dashboard)/dashboard/page.tsx[R28-40]

+/**
+ * A session can pass the top-of-page check yet expire before the data fetch
+ * re-validates it (auth token refresh race — observed live as repeated
+ * 'Not authenticated' digests on /golf/dashboard). Send that narrow case to
+ * the login screen; anything else still surfaces to the route error boundary
+ * (P002/P426 — real outages must never be swallowed).
+ */
+function redirectToLoginOnExpiredSession(error: unknown): never {
+    if (error instanceof Error && error.message === 'Not authenticated') {
+        redirect('/golf/login?returnTo=/golf/dashboard');
+    }
+    throw error;
+}
Relevance

⭐⭐⭐ High

Similar “update memory/features docs for behavior changes” was enforced/partially accepted (feature
mapping docs) in PR #296.

PR-#296

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519257 requires updating memory/features/* docs (or adding an explicit in-code
explanation) when business/user-visible behavior changes. The new redirectToLoginOnExpiredSession
function changes the dashboard’s behavior for expired sessions by redirecting to login, while the
auth feature doc does not mention this redirect behavior.

Rule 1519257: Update memory feature docs when changing business behavior
src/app/golf/(dashboard)/dashboard/page.tsx[28-40]
memory/features/auth-onboarding-join.md[1-89]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A user-visible behavior change (expired-session redirect to login with `returnTo`) was introduced on the golf dashboard route, but the corresponding `memory/features/*` documentation was not updated and the changed code does not explicitly state that no doc update is needed.

## Issue Context
The dashboard now treats the narrow `Error.message === 'Not authenticated'` case as an expired-session race and redirects to login instead of surfacing the error boundary.

## Fix Focus Areas
- src/app/golf/(dashboard)/dashboard/page.tsx[28-40]
- memory/features/auth-onboarding-join.md[1-89]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Redirect masks auth failures ✓ Resolved 🐞 Bug ☼ Reliability
Description
GolfDashboardPage now redirects to /golf/login whenever a data fetch throws Error('Not
authenticated'), but dashboard-data throws that same message even when supabase.auth.getUser() fails
(it ignores authResult.error and only checks user). This can turn real Supabase auth/network outages
into a login redirect loop instead of surfacing to the route error boundary (contrary to the
P002/P426 "must surface" intent).
Code

src/app/golf/(dashboard)/dashboard/page.tsx[R35-39]

+function redirectToLoginOnExpiredSession(error: unknown): never {
+    if (error instanceof Error && error.message === 'Not authenticated') {
+        redirect('/golf/login?returnTo=/golf/dashboard');
+    }
+    throw error;
Relevance

⭐⭐⭐ High

Repo history favors surfacing real failures vs masking: PR #208 accepted narrowing handled cases; PR
#336 pushed against error-swallowing defaults.

PR-#208
PR-#336

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The dashboard page redirects solely on an error message match, while the dashboard-data actions emit
that same message when getUser() yields no user and they do not check for/propagate
authResult.error, meaning non-expiry auth failures can be misclassified as session expiry and
redirected instead of surfaced.

src/app/golf/(dashboard)/dashboard/page.tsx[28-40]
src/app/golf/(dashboard)/dashboard/page.tsx[140-146]
src/app/golf/(dashboard)/dashboard/page.tsx[215-219]
src/app/golf/actions/dashboard-data.ts[238-252]
src/app/golf/actions/dashboard-data.ts[748-760]
src/app/golf/actions/documents.ts[258-263]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`GolfDashboardPage` redirects to login whenever it catches an `Error` with message `'Not authenticated'`. However, `getCachedCoachDashboardData` / `getCachedPlayerDashboardData` can throw that same message when `supabase.auth.getUser()` returns an **auth error** (e.g., transient failure) and `user` is null, because the action currently ignores the `error` field.

This can incorrectly redirect (and effectively swallow) real auth outages instead of letting them surface to the route `error.tsx` boundary.

### Issue Context
- `dashboard/page.tsx` uses `Promise.all(...).catch(redirectToLoginOnExpiredSession)` and redirects solely based on the thrown error message.
- `dashboard-data.ts` throws `new Error('Not authenticated')` when `user` is falsy, without checking `authResult.error`.
- Other action files in the repo explicitly treat `getUser()` returning `{ error }` as an error signal, suggesting this is a real failure mode.

### Fix Focus Areas
- src/app/golf/actions/dashboard-data.ts[238-252]
- src/app/golf/actions/dashboard-data.ts[748-760]
- src/app/golf/(dashboard)/dashboard/page.tsx[28-40]

### Suggested fix
In both dashboard-data implementations (coach + player), destructure and check the auth error explicitly:

```ts
const { data: { user }, error: authError } = authResult;
if (authError) throw authError; // surfaces outage to error boundary
if (!user) throw new Error('Not authenticated'); // true unauthenticated/expired session
```

This keeps the new redirect behavior for genuine session-expiry/no-user cases, while preserving the error-boundary behavior for real auth/network failures.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/app/golf/(dashboard)/dashboard/page.tsx
Comment thread src/app/golf/(dashboard)/dashboard/page.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/coachhelm/v2/mining/pattern-miner.ts`:
- Around line 1001-1014: Add randomized jitter to the retry delay in the
supersede update loop around `supersedeError`, while preserving the existing
attempt-based backoff and deadlock retry behavior. Compute the delay as the
current `200 * attempt` base plus a small random offset before calling
`setTimeout`, so concurrent retries are less likely to run in lock-step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4b69d5b2-16d3-44b0-a8c4-e7c07a8de63e

📥 Commits

Reviewing files that changed from the base of the PR and between 3b1c5fa and 3cb3b41.

📒 Files selected for processing (7)
  • src/app/api/cron/coachhelm-roster-sweep/route.ts
  • src/app/golf/(dashboard)/dashboard/page.tsx
  • src/instrumentation.ts
  • src/lib/coachhelm/v2/mining/pattern-miner.ts
  • src/lib/coachhelm/v2/post-round-trigger.ts
  • src/lib/coachhelm/v2/trigger-insights-bridge.ts
  • src/test/coachhelm/v2/post-round-trigger.test.ts

Comment thread src/lib/coachhelm/v2/mining/pattern-miner.ts
claude added 2 commits July 11, 2026 15:19
…ck backoff

Two follow-ups from PR #803 review:

- dashboard-data.ts: getUser() failures that supabase-js marks retryable
  (network / GoTrue 5xx) now surface to the route error boundary instead
  of being misread as 'Not authenticated' and redirected to login. A
  missing/expired session (AuthSessionMissingError, user null) still maps
  to the login redirect — applying the reviewer's literal 'throw any
  authError' suggestion would have broken the expiry redirect, since
  expiry returns an error object alongside the null user.

- pattern-miner.ts: add random jitter (0-150ms) to the supersede deadlock
  backoff so victims retried in lock-step with the concurrent miner don't
  re-collide on the same window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WJ1Gzjj9MT3UsxhMoMzSV
The dashboard's expired-session redirect (PR #803) is user-visible
behavior — note it in memory/features/auth-onboarding-join.md per the
feature-doc rule, including the outage-vs-expiry split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WJ1Gzjj9MT3UsxhMoMzSV
@njrini99-code njrini99-code merged commit 2509b3b into main Jul 11, 2026
38 checks passed
@njrini99-code njrini99-code deleted the fix/error-overview-0710 branch July 11, 2026 15:26
@coderabbitai coderabbitai Bot added the security Auth, secrets, RLS, PII, webhooks label Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

security Auth, secrets, RLS, PII, webhooks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants