-
Notifications
You must be signed in to change notification settings - Fork 0
fix: error-overview sweep — TDZ import cycle, expired-session UX, Sentry double-reports, miner deadlock retry #803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -992,14 +992,31 @@ export class PatternMiner { | |
| const playerIds = [...new Set(patterns.map((p) => p.playerId).filter(Boolean))]; | ||
| if (ids.length > 0 && playerIds.length > 0) { | ||
| const idList = `(${ids.map((id) => `"${id}"`).join(',')})`; | ||
| const { error: supersedeError } = 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)'); | ||
| // Multi-row UPDATE racing a concurrent mine's upserts/supersede over the | ||
| // same players can deadlock (observed live: 40P01 during the roster-sweep | ||
| // cron, round-submit trigger and cron batch mining the same team | ||
| // concurrently). Postgres aborts one victim wholesale and the supersede | ||
| // is idempotent (deterministic ids, converges on re-run), so a short | ||
| // retry absorbs it instead of surfacing a transient as an error. | ||
| let supersedeError: { code?: string } | null = null; | ||
| for (let attempt = 0; attempt < 3; attempt++) { | ||
| if (attempt > 0) { | ||
| // Jitter decorrelates retries from the concurrent miner we | ||
| // deadlocked with — fixed delays would re-collide in lock-step. | ||
| const backoff = 200 * attempt + Math.floor(Math.random() * 150); | ||
| await new Promise((resolve) => setTimeout(resolve, backoff)); | ||
| } | ||
| 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; | ||
|
Comment on lines
+1009
to
+1017
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the new retry loop exhausts or sees any non- Rule Used: No DELETE-then-INSERT in any save/submit/sync path... (source) Prompt To Fix With AIThis 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. |
||
| if (!error || error.code !== '40P01') break; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This only retries when the returned error has Prompt To Fix With AIThis 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. |
||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| if (supersedeError) { | ||
| await logServerError('pattern-miner.savePatterns supersede stale', { | ||
| action: 'pattern-miner.savePatterns', | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.