feat(admin): builder pool free/used capacity - #2901
Conversation
Track worker pool size as +/- capacity events and persist build start/end intervals so the admin Builder tab can show live available/running workers and reconstruct hourly free vs used. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 55 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (13)
Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_1f9dfe4a-93eb-4221-8487-7bcf9c31c638) |
|
There was a problem hiding this comment.
12 issues found across 13 files
Confidence score: 3/5
- In
supabase/functions/_backend/utils/builder_capacity.ts, utilization is currently derived fromstarted_atvalues that include queue time, andstarted_atis written with conflicting semantics insupabase/functions/_backend/triggers/cron_reconcile_build_status.ts; this can materially over/under-count true runner usage and mislead capacity decisions—normalizestarted_atto runner start semantics and enforce interval validity insupabase/migrations/20260806202503_builder_capacity_events.sql. recordBuilderCapacityIfChangedinsupabase/functions/_backend/utils/builder_capacity.tssamples only during admin stats requests and uses a non-atomic read-then-write, so missed transitions and race conditions can leave history incomplete or incorrect—persist transitions from builder-side changes and make event writes atomic/idempotent.- Long-range reconstruction in
supabase/functions/_backend/utils/builder_capacity.tsrescans run intervals per hour and does not clip first/last bins to the requested window, which risks slow/time-out admin queries and inaccurate edge-hour chart points—switch to a sweep-line reconstruction and clip bins tostartMs/endMs. - In
src/pages/admin/dashboard/builder.vue, the full-page loader waits on the external capacity call and unreachable/zero-worker states are rendered as0or hidden, so outages can look healthy and delay unrelated dashboard content—decouple page loading from capacity fetch and preserve explicit unavailable/zero-capacity UI states.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/components/admin/AdminMultiLineChart.vue">
<violation number="1" location="src/components/admin/AdminMultiLineChart.vue:70">
P3: Hourly x-axis labels use formatLocalDateTime which yields a full date+time string (dateStyle 'medium' + timeStyle 'short', e.g. "Aug 5, 2026, 10:00 PM") for every hourly point, while the chart keeps maxRotation: 0 on the x-axis. Over a multi-hour/multi-day range the long labels will crowd and overlap. Consider formatting hour labels shorter (hour-only, e.g. toLocaleTimeString with hour:'numeric'), or allowing x-axis tick rotation/autoSkip for the 'hour' granularity.</violation>
</file>
<file name="supabase/functions/_backend/utils/builder_capacity.ts">
<violation number="1" location="supabase/functions/_backend/utils/builder_capacity.ts:140">
P2: The hourly chart includes run time outside the selected date range for any non-hour-aligned range, which makes the first and last points inaccurate; clipping each bin to `startMs`/`endMs` keeps the reconstruction within the requested period.</violation>
<violation number="2" location="supabase/functions/_backend/utils/builder_capacity.ts:141">
P2: Long-range builder capacity requests repeatedly rescan all build intervals for every hour, making reconstruction O(hours × runs) and risking slow or timed-out admin stats at 6–12 month ranges. A single sweep-line pass or database-side hourly aggregation would preserve the chart while avoiding repeated work.</violation>
<violation number="3" location="supabase/functions/_backend/utils/builder_capacity.ts:291">
P2: recordBuilderCapacityIfChanged performs a non-atomic read-then-write: it SELECTs the latest event to compute `delta`, then INSERTs a new row. When two admin_stats calls (e.g. two open builder tabs, or a poll racing a page load) run concurrently, both read the same `previous` workers_total and both insert a new row with the same absolute `workers_total` but a duplicated, non-zero `delta`. The event log then contains two consecutive rows with an unchanged `workers_total` yet a non-zero delta, corrupting the reconstructed pool series (`workersAt`/delta history) that this whole feature is built on. Consider making the write atomic (e.g. a single INSERT ... ON CONFLICT / advisory lock, or compute the delta from the max(created_at) row within the same transaction) so concurrent writers cannot derive the delta from a stale read.</violation>
<violation number="4" location="supabase/functions/_backend/utils/builder_capacity.ts:384">
P1: Hourly `used` capacity includes queue time rather than only runner execution: `build_requests.started_at` is populated at submission, while `waiting_runner` jobs can remain unassigned. Reconstruct from the builder-reported start (or exclude the pre-run portion) so waiting jobs do not consume worker capacity in the chart.</violation>
<violation number="5" location="supabase/functions/_backend/utils/builder_capacity.ts:450">
P3: In the degraded path (source == 'ok' or builder unreachable) `used` is approximated from `countActiveBuilds`, which counts build_requests in `['starting', 'waiting_runner', 'running']`. A build in `waiting_runner` is queued awaiting a runner, not currently occupying a machine, so including it inflates the live "Used" card and deflates "Free" whenever the pool has queued work. Consider excluding `waiting_runner` from the used estimate (or counting only `running`/`starting`) so the fallback reflects machines actually consumed rather than demand queued behind them.</violation>
<violation number="6" location="supabase/functions/_backend/utils/builder_capacity.ts:455">
P2: Capacity history is sampled only when an admin opens/refreshes the dashboard, so changes between admin stats requests are lost and the stored-event fallback cannot reconstruct those hours. Persist capacity transitions from the builder sync/lifecycle path independently of the read-only stats request.</violation>
</file>
<file name="supabase/migrations/20260806202503_builder_capacity_events.sql">
<violation number="1" location="supabase/migrations/20260806202503_builder_capacity_events.sql:5">
P2: Invalid build intervals can be stored and silently undercount utilization because the reconstruction code discards completions at or before their start. A table check such as `started_at IS NULL OR completed_at IS NULL OR completed_at >= started_at` would keep persisted run intervals valid.</violation>
</file>
<file name="src/pages/admin/dashboard/builder.vue">
<violation number="1" location="src/pages/admin/dashboard/builder.vue:296">
P2: A valid period with zero workers is treated as no data because the chart guard requires a positive value, hiding the zero-capacity/outage timeline and contradicting the presence of capacity events. Base this guard on event/run presence (or equivalent metadata), not on a positive plotted value.</violation>
<violation number="2" location="src/pages/admin/dashboard/builder.vue:406">
P2: A slow or unreachable builder request now keeps the entire Builder page behind `PageLoader` until the external capacity call settles, delaying the rest of the admin dashboard even though capacity has its own loading state. Start capacity loading independently of the initial page readiness so the other dashboard sections can render while the live card/chart is pending.</violation>
<violation number="3" location="src/pages/admin/dashboard/builder.vue:449">
P2: When the builder is unavailable, every live card shows `0` even though the response explicitly says `builder_reachable: false`, making an outage look like a healthy pool with no workers or jobs. Preserve the unreachable state and render an unknown value/degraded label for all live metrics until a valid snapshot is available.</violation>
</file>
<file name="supabase/functions/_backend/triggers/cron_reconcile_build_status.ts">
<violation number="1" location="supabase/functions/_backend/triggers/cron_reconcile_build_status.ts:229">
P2: This write defines `started_at` as the builder-reported run start, but the same column is set elsewhere to the submission wall-clock time: `start.ts` writes `started_at: new Date().toISOString()` when the build advances to `startedStatus`. Because this cron reconciles every non-terminal stale build (~1/min) and overwrites `started_at` even on ticks where no transition occurred, a queued build's stored `started_at` silently changes from its queue-entry time to the later run-start once a runner picks it up. `reconstructHourlyCapacity`/the `builder_capacity` SQL feed "used"/free off these `started_at`/`completed_at` intervals, so the hourly capacity numbers become non-deterministic and depend on which writer ran last and how long the build sat in the queue.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| }>( | ||
| `WITH request_runs AS ( | ||
| SELECT | ||
| br.started_at, |
There was a problem hiding this comment.
P1: Hourly used capacity includes queue time rather than only runner execution: build_requests.started_at is populated at submission, while waiting_runner jobs can remain unassigned. Reconstruct from the builder-reported start (or exclude the pre-run portion) so waiting jobs do not consume worker capacity in the chart.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/builder_capacity.ts, line 384:
<comment>Hourly `used` capacity includes queue time rather than only runner execution: `build_requests.started_at` is populated at submission, while `waiting_runner` jobs can remain unassigned. Reconstruct from the builder-reported start (or exclude the pre-run portion) so waiting jobs do not consume worker capacity in the chart.</comment>
<file context>
@@ -0,0 +1,471 @@
+ }>(
+ `WITH request_runs AS (
+ SELECT
+ br.started_at,
+ br.completed_at,
+ br.created_at,
</file context>
| } | ||
|
|
||
| const poolSize = live.workers_online > 0 ? live.workers_online : live.workers_total | ||
| if (live.builder_reachable) |
There was a problem hiding this comment.
P2: Capacity history is sampled only when an admin opens/refreshes the dashboard, so changes between admin stats requests are lost and the stored-event fallback cannot reconstruct those hours. Persist capacity transitions from the builder sync/lifecycle path independently of the read-only stats request.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/builder_capacity.ts, line 455:
<comment>Capacity history is sampled only when an admin opens/refreshes the dashboard, so changes between admin stats requests are lost and the stored-event fallback cannot reconstruct those hours. Persist capacity transitions from the builder sync/lifecycle path independently of the read-only stats request.</comment>
<file context>
@@ -0,0 +1,471 @@
+ }
+
+ const poolSize = live.workers_online > 0 ? live.workers_online : live.workers_total
+ if (live.builder_reachable)
+ await recordBuilderCapacityIfChanged(c, poolSize, `admin_${source}`)
+
</file context>
| const hourEnd = t + HOUR_MS | ||
| const date = new Date(t).toISOString() | ||
| const workers = workersAt(sortedEvents, hourEnd - 1) | ||
| const used = maxConcurrentUsed(intervals, t, hourEnd) |
There was a problem hiding this comment.
P2: Long-range builder capacity requests repeatedly rescan all build intervals for every hour, making reconstruction O(hours × runs) and risking slow or timed-out admin stats at 6–12 month ranges. A single sweep-line pass or database-side hourly aggregation would preserve the chart while avoiding repeated work.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/builder_capacity.ts, line 141:
<comment>Long-range builder capacity requests repeatedly rescan all build intervals for every hour, making reconstruction O(hours × runs) and risking slow or timed-out admin stats at 6–12 month ranges. A single sweep-line pass or database-side hourly aggregation would preserve the chart while avoiding repeated work.</comment>
<file context>
@@ -0,0 +1,471 @@
+ const hourEnd = t + HOUR_MS
+ const date = new Date(t).toISOString()
+ const workers = workersAt(sortedEvents, hourEnd - 1)
+ const used = maxConcurrentUsed(intervals, t, hourEnd)
+ const free = Math.max(0, workers - used)
+ points.push({
</file context>
| for (let t = hourStart; t < endMs; t += HOUR_MS) { | ||
| const hourEnd = t + HOUR_MS | ||
| const date = new Date(t).toISOString() | ||
| const workers = workersAt(sortedEvents, hourEnd - 1) |
There was a problem hiding this comment.
P2: The hourly chart includes run time outside the selected date range for any non-hour-aligned range, which makes the first and last points inaccurate; clipping each bin to startMs/endMs keeps the reconstruction within the requested period.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/builder_capacity.ts, line 140:
<comment>The hourly chart includes run time outside the selected date range for any non-hour-aligned range, which makes the first and last points inaccurate; clipping each bin to `startMs`/`endMs` keeps the reconstruction within the requested period.</comment>
<file context>
@@ -0,0 +1,471 @@
+ for (let t = hourStart; t < endMs; t += HOUR_MS) {
+ const hourEnd = t + HOUR_MS
+ const date = new Date(t).toISOString()
+ const workers = workersAt(sortedEvents, hourEnd - 1)
+ const used = maxConcurrentUsed(intervals, t, hourEnd)
+ const free = Math.max(0, workers - used)
</file context>
| -- on build_requests. Hourly free/used is reconstructed from these events — | ||
| -- no polling cron snapshots. | ||
|
|
||
| ALTER TABLE public.build_requests |
There was a problem hiding this comment.
P2: Invalid build intervals can be stored and silently undercount utilization because the reconstruction code discards completions at or before their start. A table check such as started_at IS NULL OR completed_at IS NULL OR completed_at >= started_at would keep persisted run intervals valid.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/migrations/20260806202503_builder_capacity_events.sql, line 5:
<comment>Invalid build intervals can be stored and silently undercount utilization because the reconstruction code discards completions at or before their start. A table check such as `started_at IS NULL OR completed_at IS NULL OR completed_at >= started_at` would keep persisted run intervals valid.</comment>
<file context>
@@ -0,0 +1,65 @@
+-- on build_requests. Hourly free/used is reconstructed from these events —
+-- no polling cron snapshots.
+
+ALTER TABLE public.build_requests
+ ADD COLUMN IF NOT EXISTS started_at timestamp with time zone,
+ ADD COLUMN IF NOT EXISTS completed_at timestamp with time zone;
</file context>
| // ---- shared lifecycle ---- | ||
| async function loadAll() { | ||
| await Promise.all([loadGlobalStatsTrend(), loadData()]) | ||
| await Promise.all([loadCapacity(), loadGlobalStatsTrend(), loadData()]) |
There was a problem hiding this comment.
P2: A slow or unreachable builder request now keeps the entire Builder page behind PageLoader until the external capacity call settles, delaying the rest of the admin dashboard even though capacity has its own loading state. Start capacity loading independently of the initial page readiness so the other dashboard sections can render while the live card/chart is pending.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/pages/admin/dashboard/builder.vue, line 406:
<comment>A slow or unreachable builder request now keeps the entire Builder page behind `PageLoader` until the external capacity call settles, delaying the rest of the admin dashboard even though capacity has its own loading state. Start capacity loading independently of the initial page readiness so the other dashboard sections can render while the live card/chart is pending.</comment>
<file context>
@@ -349,7 +403,7 @@ async function spoof(orgId: string) {
// ---- shared lifecycle ----
async function loadAll() {
- await Promise.all([loadGlobalStatsTrend(), loadData()])
+ await Promise.all([loadCapacity(), loadGlobalStatsTrend(), loadData()])
}
</file context>
| await Promise.all([loadCapacity(), loadGlobalStatsTrend(), loadData()]) | |
| void loadCapacity() | |
| await Promise.all([loadGlobalStatsTrend(), loadData()]) |
| status: effectiveStatus, | ||
| last_error: effectiveError, | ||
| runner_wait_seconds: runnerWaitSeconds, | ||
| started_at: isoFromBuilderTimestamp(builderJob.job.started_at) ?? undefined, |
There was a problem hiding this comment.
P2: This write defines started_at as the builder-reported run start, but the same column is set elsewhere to the submission wall-clock time: start.ts writes started_at: new Date().toISOString() when the build advances to startedStatus. Because this cron reconciles every non-terminal stale build (~1/min) and overwrites started_at even on ticks where no transition occurred, a queued build's stored started_at silently changes from its queue-entry time to the later run-start once a runner picks it up. reconstructHourlyCapacity/the builder_capacity SQL feed "used"/free off these started_at/completed_at intervals, so the hourly capacity numbers become non-deterministic and depend on which writer ran last and how long the build sat in the queue.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/triggers/cron_reconcile_build_status.ts, line 229:
<comment>This write defines `started_at` as the builder-reported run start, but the same column is set elsewhere to the submission wall-clock time: `start.ts` writes `started_at: new Date().toISOString()` when the build advances to `startedStatus`. Because this cron reconciles every non-terminal stale build (~1/min) and overwrites `started_at` even on ticks where no transition occurred, a queued build's stored `started_at` silently changes from its queue-entry time to the later run-start once a runner picks it up. `reconstructHourlyCapacity`/the `builder_capacity` SQL feed "used"/free off these `started_at`/`completed_at` intervals, so the hourly capacity numbers become non-deterministic and depend on which writer ran last and how long the build sat in the queue.</comment>
<file context>
@@ -225,6 +226,8 @@ app.post('/', middlewareAPISecret, async (c) => {
status: effectiveStatus,
last_error: effectiveError,
runner_wait_seconds: runnerWaitSeconds,
+ started_at: isoFromBuilderTimestamp(builderJob.job.started_at) ?? undefined,
+ completed_at: isoFromBuilderTimestamp(effectiveCompletedAt) ?? undefined,
updated_at: new Date().toISOString(),
</file context>
| return null | ||
|
|
||
| const delta = previous === null ? total : total - previous | ||
| const { data: inserted, error: insertError } = await admin |
There was a problem hiding this comment.
P2: recordBuilderCapacityIfChanged performs a non-atomic read-then-write: it SELECTs the latest event to compute delta, then INSERTs a new row. When two admin_stats calls (e.g. two open builder tabs, or a poll racing a page load) run concurrently, both read the same previous workers_total and both insert a new row with the same absolute workers_total but a duplicated, non-zero delta. The event log then contains two consecutive rows with an unchanged workers_total yet a non-zero delta, corrupting the reconstructed pool series (workersAt/delta history) that this whole feature is built on. Consider making the write atomic (e.g. a single INSERT ... ON CONFLICT / advisory lock, or compute the delta from the max(created_at) row within the same transaction) so concurrent writers cannot derive the delta from a stale read.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/builder_capacity.ts, line 291:
<comment>recordBuilderCapacityIfChanged performs a non-atomic read-then-write: it SELECTs the latest event to compute `delta`, then INSERTs a new row. When two admin_stats calls (e.g. two open builder tabs, or a poll racing a page load) run concurrently, both read the same `previous` workers_total and both insert a new row with the same absolute `workers_total` but a duplicated, non-zero `delta`. The event log then contains two consecutive rows with an unchanged `workers_total` yet a non-zero delta, corrupting the reconstructed pool series (`workersAt`/delta history) that this whole feature is built on. Consider making the write atomic (e.g. a single INSERT ... ON CONFLICT / advisory lock, or compute the delta from the max(created_at) row within the same transaction) so concurrent writers cannot derive the delta from a stale read.</comment>
<file context>
@@ -0,0 +1,471 @@
+ return null
+
+ const delta = previous === null ? total : total - previous
+ const { data: inserted, error: insertError } = await admin
+ .from('builder_capacity_events')
+ .insert({
</file context>
| if (formattedMonth) | ||
| return formattedMonth | ||
| } | ||
| if (props.dateGranularity === 'hour') { |
There was a problem hiding this comment.
P3: Hourly x-axis labels use formatLocalDateTime which yields a full date+time string (dateStyle 'medium' + timeStyle 'short', e.g. "Aug 5, 2026, 10:00 PM") for every hourly point, while the chart keeps maxRotation: 0 on the x-axis. Over a multi-hour/multi-day range the long labels will crowd and overlap. Consider formatting hour labels shorter (hour-only, e.g. toLocaleTimeString with hour:'numeric'), or allowing x-axis tick rotation/autoSkip for the 'hour' granularity.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/admin/AdminMultiLineChart.vue, line 70:
<comment>Hourly x-axis labels use formatLocalDateTime which yields a full date+time string (dateStyle 'medium' + timeStyle 'short', e.g. "Aug 5, 2026, 10:00 PM") for every hourly point, while the chart keeps maxRotation: 0 on the x-axis. Over a multi-hour/multi-day range the long labels will crowd and overlap. Consider formatting hour labels shorter (hour-only, e.g. toLocaleTimeString with hour:'numeric'), or allowing x-axis tick rotation/autoSkip for the 'hour' granularity.</comment>
<file context>
@@ -67,6 +67,11 @@ function formatChartDate(date: string) {
if (formattedMonth)
return formattedMonth
}
+ if (props.dateGranularity === 'hour') {
+ const formattedHour = formatLocalDateTime(date)
+ if (formattedHour)
</file context>
| // Prefer builder machine occupancy; fall back to Capgo active jobs when /ok | ||
| // path cannot see currentJobId. | ||
| if (source === 'ok' || (!live.builder_reachable && activeBuilds > 0)) { | ||
| live.used = Math.min(live.workers_online || activeBuilds, activeBuilds) |
There was a problem hiding this comment.
P3: In the degraded path (source == 'ok' or builder unreachable) used is approximated from countActiveBuilds, which counts build_requests in ['starting', 'waiting_runner', 'running']. A build in waiting_runner is queued awaiting a runner, not currently occupying a machine, so including it inflates the live "Used" card and deflates "Free" whenever the pool has queued work. Consider excluding waiting_runner from the used estimate (or counting only running/starting) so the fallback reflects machines actually consumed rather than demand queued behind them.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/builder_capacity.ts, line 450:
<comment>In the degraded path (source == 'ok' or builder unreachable) `used` is approximated from `countActiveBuilds`, which counts build_requests in `['starting', 'waiting_runner', 'running']`. A build in `waiting_runner` is queued awaiting a runner, not currently occupying a machine, so including it inflates the live "Used" card and deflates "Free" whenever the pool has queued work. Consider excluding `waiting_runner` from the used estimate (or counting only `running`/`starting`) so the fallback reflects machines actually consumed rather than demand queued behind them.</comment>
<file context>
@@ -0,0 +1,471 @@
+ // Prefer builder machine occupancy; fall back to Capgo active jobs when /ok
+ // path cannot see currentJobId.
+ if (source === 'ok' || (!live.builder_reachable && activeBuilds > 0)) {
+ live.used = Math.min(live.workers_online || activeBuilds, activeBuilds)
+ live.free = Math.max(0, (live.workers_online || activeBuilds) - live.used)
+ }
</file context>



Summary (AI generated)
builder_capacity_events(+/− worker pool size over time) and persistbuild_requests.started_at/completed_atadmin_statscategorybuilder_capacity: live available/running from builder runners, hourly free/used reconstructed from capacity events + build intervalsMotivation (AI generated)
Need to see current free vs busy builders and historical hourly utilization without cron snapshots — event log of worker count plus existing build start/end is enough to reconstruct the series.
Business Impact (AI generated)
Faster ops visibility into builder saturation and queue pressure, so we can scale MacInCloud runners before customers wait.
Test Plan (AI generated)
bun test:unitincludestests/builder-capacity.unit.test.tsand admin_stats schema acceptancebun run supabase:db:resetapplies20260806202503_builder_capacity_events.sql/admin/dashboard/builder/as platform admin — see Available / Running / Online / Waiting / Offline cardsBUILDER_URLunreachable, cards show builder unreachable and chart still uses stored events/logsGenerated with AI
Made with Cursor
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.