Skip to content

fix(backend): harden cron_stat_app follow-up failures#1923

Merged
riderx merged 4 commits into
mainfrom
codex/fix-posthog-cron-stat-followup
Apr 21, 2026
Merged

fix(backend): harden cron_stat_app follow-up failures#1923
riderx merged 4 commits into
mainfrom
codex/fix-posthog-cron-stat-followup

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Apr 21, 2026

Summary (AI generated)

  • make the cron_stat_app org timestamp refresh best-effort after stats rows are already written
  • make the follow-up queue_cron_stat_org_for_org call best-effort so transient PostgREST failures do not turn a successful stats refresh into a 5xx
  • add a unit regression test covering both the org refresh failure path and the plan queue failure path

Motivation (AI generated)

PostHog still shows a backend error bucket on POST /triggers/cron_stat_app with PostgrestError 502 responses. The trigger completes the core stats upserts first, then performs secondary org refresh and plan queue work. A transient failure in that trailing follow-up step should not fail the whole request after the primary stats write already succeeded.

Business Impact (AI generated)

This reduces noisy backend failures in PostHog and keeps usage/stat refreshes reliable for billing and reporting flows even when the follow-up queue path is temporarily flaky. It lowers false-negative cron failures without changing the successful data path.

Test Plan (AI generated)

  • bunx vitest run tests/cron_stat_app_followup.unit.test.ts
  • bun run lint:backend
  • bun run typecheck
  • GitHub Actions

Generated with AI

Summary by CodeRabbit

  • Bug Fixes
    • Scheduled stats sync now logs failures when loading or persisting org metadata without aborting the endpoint. Follow-up queuing is only attempted for orgs with a valid customer ID; queue RPC failures can still surface from the handler.
  • Tests
    • Added unit tests simulating queue, org-update, and org-lookup failures to verify resilient behavior and expected HTTP responses.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

Extracted org-metadata fetch, stats-timestamp persistence, and queueing into helpers; persistence errors are logged (not fatal) and queuing is performed only when a customerId is present.

Changes

Cohort / File(s) Summary
Cron Stats Trigger
supabase/functions/_backend/triggers/cron_stat_app.ts
Added helpers (getOrgStatsRefreshTarget, syncOrgStatsRefresh, queueOrgPlanRefresh); moved org selection and timestamp update into helpers; log persistence errors via cloudlogErr instead of aborting; gate RPC queuing on customerId.
Unit tests — follow-up failure scenarios
tests/cron_stat_app_followup.unit.test.ts
New Vitest suite adding detailed Supabase client and RPC stubs/mocks and three scenarios: queue RPC failure after stats writes (expects 500), org timestamp update failure after stats writes (logs error, expects 200), and org lookup failure (logs, expects 200).

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (cron request)
  participant Handler as Cron Handler
  participant DB as Supabase DB
  participant RPC as queue_cron_stat_org_for_org
  participant Logger as cloudlogErr

  Client->>Handler: POST /cron_stat_app
  Handler->>DB: getOrgStatsRefreshTarget(org_id)
  DB-->>Handler: { customerId, previousStatsUpdatedAt } / error
  opt loaded
    Handler->>DB: syncOrgStatsRefresh(...) (persist timestamps)
    alt persist succeeds
      DB-->>Handler: success
    else persist fails
      DB-->>Handler: error
      Handler->>Logger: cloudlogErr(error)
    end
    alt customerId exists
      Handler->>RPC: queueOrgPlanRefresh(customerId)
      RPC-->>Handler: success / error
    end
  end
  Handler-->>Client: HTTP 200 | 500 (depends on queue RPC)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Ari4ka

Poem

🐰 I hopped through code with ears alert and spry,

I split the tasks so each one knows its part,
If a write should falter, I log it, not cry,
And call the queue only when customerId's nigh,
A tidy hop, a calm and clever heart.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and specifically describes the main change: hardening the cron_stat_app to handle follow-up failures gracefully without aborting.
Description check ✅ Passed The pull request description includes a comprehensive summary, motivation, business impact, and a detailed test plan with all required tests marked as completed. It follows the template structure with all critical sections filled out.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 codex/fix-posthog-cron-stat-followup

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Apr 21, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing codex/fix-posthog-cron-stat-followup (940ff73) with main (2d57499)

Open in CodSpeed

@riderx riderx marked this pull request as ready for review April 21, 2026 12:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/cron_stat_app_followup.unit.test.ts (1)

98-119: The queue-based builder dispatch is fragile.

The orgBuilders array relies on the exact call order in production code (first from('orgs').select(), then from('orgs').update()). If the production code changes order or adds another orgs query, this test silently breaks.

Consider using a more explicit dispatch based on the method called:

♻️ Suggested improvement for more robust mocking
-  const orgBuilders = [orgSelectBuilder, orgUpdateBuilder]
-
   const client = {
     from: vi.fn((table: string) => {
       switch (table) {
         // ...
         case 'orgs': {
-          const nextBuilder = orgBuilders.shift()
-          if (!nextBuilder) {
-            throw new Error('Unexpected orgs builder call')
-          }
-          return nextBuilder
+          // Return a combined builder that routes based on method called
+          return {
+            select: vi.fn().mockReturnValue(orgSelectBuilder),
+            update: vi.fn().mockReturnValue(orgUpdateBuilder),
+          }
         }
         // ...
       }
     }),

This way, the mock is method-aware rather than call-order-aware.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cron_stat_app_followup.unit.test.ts` around lines 98 - 119, The test's
client.from mock uses a fragile call-order queue (orgBuilders) for 'orgs' which
breaks if production call order changes; replace the queue with a method-aware
dispatch: when client.from('orgs') is invoked return an object whose select and
update methods delegate to orgSelectBuilder and orgUpdateBuilder respectively
(use orgSelectBuilder for select calls and orgUpdateBuilder for update calls) so
the mock behavior depends on the method invoked (select/update) rather than the
sequence in orgBuilders or call order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/cron_stat_app_followup.unit.test.ts`:
- Around line 98-119: The test's client.from mock uses a fragile call-order
queue (orgBuilders) for 'orgs' which breaks if production call order changes;
replace the queue with a method-aware dispatch: when client.from('orgs') is
invoked return an object whose select and update methods delegate to
orgSelectBuilder and orgUpdateBuilder respectively (use orgSelectBuilder for
select calls and orgUpdateBuilder for update calls) so the mock behavior depends
on the method invoked (select/update) rather than the sequence in orgBuilders or
call order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ea352952-6b0c-42b3-a92e-f6d4b2d20065

📥 Commits

Reviewing files that changed from the base of the PR and between 2d57499 and b2561ee.

📒 Files selected for processing (2)
  • supabase/functions/_backend/triggers/cron_stat_app.ts
  • tests/cron_stat_app_followup.unit.test.ts

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2561ee8f2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/functions/_backend/triggers/cron_stat_app.ts Outdated
Comment thread supabase/functions/_backend/triggers/cron_stat_app.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/cron_stat_app_followup.unit.test.ts (1)

136-142: Regression coverage should also prove primary stats writes were executed.

Current assertions only verify follow-up paths (orgs update + queue RPC). To protect the “stats rows already written” guarantee, assert daily upsert write calls too.

Suggested enhancement
   return {
     client,
     builders: {
+      dailyMauBuilder,
+      dailyBandwidthBuilder,
+      dailyStorageBuilder,
+      dailyVersionBuilder,
       orgUpdateBuilder,
       queueBuilder,
     },
   }
@@
     expect(response.status).toBe(500)
+    expect(builders.dailyMauBuilder.throwOnError).toHaveBeenCalledTimes(1)
+    expect(builders.dailyBandwidthBuilder.throwOnError).toHaveBeenCalledTimes(1)
+    expect(builders.dailyStorageBuilder.throwOnError).toHaveBeenCalledTimes(1)
+    expect(builders.dailyVersionBuilder.throwOnError).toHaveBeenCalledTimes(1)
     expect(builders.orgUpdateBuilder.throwOnError).toHaveBeenCalledTimes(1)
     expect(builders.queueBuilder.throwOnError).toHaveBeenCalledTimes(1)
@@
     expect(response.status).toBe(200)
+    expect(builders.dailyMauBuilder.throwOnError).toHaveBeenCalledTimes(1)
+    expect(builders.dailyBandwidthBuilder.throwOnError).toHaveBeenCalledTimes(1)
+    expect(builders.dailyStorageBuilder.throwOnError).toHaveBeenCalledTimes(1)
+    expect(builders.dailyVersionBuilder.throwOnError).toHaveBeenCalledTimes(1)
     expect(builders.orgUpdateBuilder.throwOnError).toHaveBeenCalledTimes(1)
     expect(builders.queueBuilder.throwOnError).toHaveBeenCalledTimes(1)

Also applies to: 203-205, 231-233

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cron_stat_app_followup.unit.test.ts` around lines 136 - 142, The test
currently only asserts follow-up behavior (orgUpdateBuilder + queueBuilder) but
misses verifying that the primary daily stats upsert was executed; update the
test to assert the mocked stats write was called (e.g., assert the mock on the
client/stats writer — such as client.upsertDailyStats or the stats mock used in
the factory — received the expected calls) alongside the existing
orgUpdateBuilder and queueBuilder assertions; add equivalent assertions in the
other two test sections that the comment references so each block verifies the
daily upsert write calls as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/cron_stat_app_followup.unit.test.ts`:
- Around line 182-208: The test asserts queuing failure is fatal but the
intended behavior is best-effort; update the assertions in the 'returns 500 when
queuing plan processing fails after stats writes' test so that a queue error
from createSupabaseStub (builders.queueBuilder.throwOnError) does not make the
request fail: change expect(response.status).toBe(500) to
expect(response.status).toBe(200) (or to a success status used elsewhere), keep
the check that builders.orgUpdateBuilder.throwOnError was called once and that
builders.queueBuilder.throwOnError was invoked, and remove or adjust the payload
error assertion (remove expect(payload.error).toBe('unknown_error') and any
checks that treat the queue error as a fatal error) so the test reflects the
best-effort contract for queue_cron_stat_org_for_org.

---

Nitpick comments:
In `@tests/cron_stat_app_followup.unit.test.ts`:
- Around line 136-142: The test currently only asserts follow-up behavior
(orgUpdateBuilder + queueBuilder) but misses verifying that the primary daily
stats upsert was executed; update the test to assert the mocked stats write was
called (e.g., assert the mock on the client/stats writer — such as
client.upsertDailyStats or the stats mock used in the factory — received the
expected calls) alongside the existing orgUpdateBuilder and queueBuilder
assertions; add equivalent assertions in the other two test sections that the
comment references so each block verifies the daily upsert write calls as well.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89f034b0-7362-4b40-8670-9a5646419cdf

📥 Commits

Reviewing files that changed from the base of the PR and between b2561ee and 270e734.

📒 Files selected for processing (2)
  • supabase/functions/_backend/triggers/cron_stat_app.ts
  • tests/cron_stat_app_followup.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • supabase/functions/_backend/triggers/cron_stat_app.ts

Comment thread tests/cron_stat_app_followup.unit.test.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d146c81585

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/functions/_backend/triggers/cron_stat_app.ts
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@supabase/functions/_backend/triggers/cron_stat_app.ts`:
- Around line 209-210: The call to queueOrgPlanRefresh(c, supabase, body.orgId,
orgStatsRefreshTarget.customerId) must be made best-effort so transient
PostgREST failures don't turn the whole job into a 5xx; wrap that await in a
try/catch (or equivalent non-throwing guard) and swallow/log the error instead
of letting queueOrgPlanRefresh's .throwOnError() propagate; reference the
queueOrgPlanRefresh invocation and ensure any thrown error is caught and logged
(with context: orgId and customerId) but does not rethrow.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a49694e6-75a6-4970-ba48-72ee7a98de29

📥 Commits

Reviewing files that changed from the base of the PR and between d146c81 and 940ff73.

📒 Files selected for processing (2)
  • supabase/functions/_backend/triggers/cron_stat_app.ts
  • tests/cron_stat_app_followup.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/cron_stat_app_followup.unit.test.ts

Comment thread supabase/functions/_backend/triggers/cron_stat_app.ts
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 209f599 into main Apr 21, 2026
15 checks passed
@riderx riderx deleted the codex/fix-posthog-cron-stat-followup branch April 21, 2026 13:08
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.

1 participant