Skip to content

[codex] Align Hyperping cron healthchecks#2340

Merged
riderx merged 1 commit into
mainfrom
codex/hyperping-cron-healthcheck-start-grace
May 26, 2026
Merged

[codex] Align Hyperping cron healthchecks#2340
riderx merged 1 commit into
mainfrom
codex/hyperping-cron-healthcheck-start-grace

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented May 26, 2026

Summary (AI generated)

  • Send Hyperping /start pings once per cron queue run before queue processing starts.
  • Keep the existing completion ping after successful empty queue drains.
  • Update the Hyperping healthcheck backfill script so grace periods use a one-minute floor and never send second-based grace values that Hyperping displays incorrectly.
  • Add unit coverage for /start URL generation and healthcheck period/grace generation.

Motivation (AI generated)

Cron queue failures were visible in queue failure alerts, but Hyperping could stay green because only completion pings were sent and the configured grace period could let a later run satisfy an earlier missed completion window. The healthcheck creation script also generated 59 seconds grace for one-minute jobs, which Hyperping surfaced as 59 minutes in the UI.

Business Impact (AI generated)

This makes cron monitoring more trustworthy for production operations. Capgo should get clearer downtime signals when queue-backed cron jobs stall or fail, reducing silent operational risk around manifest processing and other scheduled backend work.

Test Plan (AI generated)

  • bunx vitest run tests/create-hyperping-cron-healthchecks.unit.test.ts tests/queue-consumer-message-shape.unit.test.ts
  • bun run typecheck:backend
  • bun run lint:backend
  • git diff --check
  • Commit hook full typecheck: bun run cli:typecheck && bun run typecheck:backend && bun run typecheck:frontend
  • Local Supabase migration apply via bun run supabase:start
  • bun run supabase:with-env -- bunx vitest run tests/cron-healthchecks.test.ts tests/security-definer-execute-hardening.test.ts

Summary by CodeRabbit

  • New Features

    • Cron healthchecks now automatically ping a startup endpoint before queue processing begins.
  • Bug Fixes

    • Cron healthcheck grace periods are now intelligently derived from task cadence with proper boundary adjustments.
  • Chores

    • Refactored healthcheck payload generation for improved configuration and testability.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

📝 Walkthrough

Walkthrough

This PR refactors cron healthcheck grace computation to derive values from each task's scheduled cadence, exports the payload builder and main function for testing, adds optional /start endpoint pinging in the queue consumer before processing, and adds comprehensive unit tests.

Changes

Cron Healthcheck Grace Computation and Start Ping

Layer / File(s) Summary
Grace period computation and buildHealthcheckPayload
scripts/create_hyperping_cron_healthchecks.ts
Helper functions compute cadence seconds from cron row fields, derive grace periods capped and floored to Hyperping boundaries, and format as period strings. Candidate now includes a computed grace field. buildHealthcheckPayload is exported and refactored to accept (row, maxGracePeriodSeconds, timezone) instead of explicit grace inputs.
Entry-point and call-site wiring in create_hyperping
scripts/create_hyperping_cron_healthchecks.ts
main is exported. Grace inputs from CLI are converted to maxGracePeriodSeconds and passed through to buildHealthcheckPayload. Dry-run logging includes the computed grace. Module execution is guarded so main() only runs when the script is executed directly, not when imported.
Queue consumer start ping helpers and integration
supabase/functions/_backend/triggers/queue_consumer.ts
New helpers normalize healthcheck base URLs and derive /start endpoints, then conditionally ping via GET with existing healthcheck timeout/abort behavior. /sync body parsing trims healthcheck_url and converts empty/whitespace-only values to null. Queue processing calls maybePingCronHealthcheckStart before processQueue when healthcheck_url is non-null. New helpers are exported in __queueConsumerTestUtils__ for tests.
Unit tests for buildHealthcheckPayload
tests/create-hyperping-cron-healthchecks.unit.test.ts
Vitest suite with concurrent tests validating computed period and grace values for second-, minute-, hour-, and daily-based schedules. Includes createCronTaskRow helper to construct test objects.
Unit test for maybePingCronHealthcheckStart
tests/queue-consumer-message-shape.unit.test.ts
Concurrent test verifies the function returns success (reported === true), normalizes base URL with trailing slash into /start endpoint, calls fetch exactly once, and issues a GET request to the normalized endpoint.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Cap-go/capgo#2335: Both PRs touch scripts/create_hyperping_cron_healthchecks.ts; this PR refactors buildHealthcheckPayload and main and changes how grace is computed for each cron task, directly evolving the backfill script added in that PR.
  • Cap-go/capgo#2324: This PR's queue_consumer.ts changes (normalizing healthcheck_url, deriving /start endpoint, conditionally GETing it) build on that PR's addition of cron healthcheck_url support and ping-helper/test-utility logic.
🚥 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 title '[codex] Align Hyperping cron healthchecks' clearly summarizes the main change: updating Hyperping cron healthcheck logic for alignment, matching the core objective of syncing start pings and grace period calculations.
Description check ✅ Passed The description comprehensively covers all template sections: a clear summary of changes, explicit motivation, business impact, and a detailed test plan with all steps verified. The AI-generated sections effectively document the PR objectives.
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

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 26, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/hyperping-cron-healthcheck-start-grace (8402bb9) with main (bb02296)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@riderx riderx force-pushed the codex/hyperping-cron-healthcheck-start-grace branch from a278c41 to 98ebb63 Compare May 26, 2026 16:51
@riderx riderx marked this pull request as ready for review May 26, 2026 17:00
@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.

@coderabbitai coderabbitai Bot added the codex label May 26, 2026
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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql`:
- Around line 31-37: The function process_queue_with_healthcheck currently does
a dynamic EXECUTE of 'SELECT count(*) FROM pgmq.%I' for each queue_name
(populating queue_size), which causes full-table scans; replace that per-queue
count with a call to the existing metrics function (e.g. SELECT queue_length
FROM pgmq.metrics(queue_name)) and cast/convert that result into queue_size
(int) to drive the calls_needed computation, updating any variable names
(queue_size) and logic that rely on the previous count; after changing, run
EXPLAIN (ANALYZE, BUFFERS) comparing the new metrics-based lookup vs the old
count(*) on representative queues to validate performance.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 997d7ae2-739b-4a85-afba-4c675720e35a

📥 Commits

Reviewing files that changed from the base of the PR and between 46cab33 and 98ebb63.

📒 Files selected for processing (5)
  • scripts/create_hyperping_cron_healthchecks.ts
  • supabase/functions/_backend/triggers/queue_consumer.ts
  • supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql
  • tests/create-hyperping-cron-healthchecks.unit.test.ts
  • tests/queue-consumer-message-shape.unit.test.ts

Comment thread supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql Outdated
@thefuturrjfhejh
Copy link
Copy Markdown

I checked the new migration against the stored schema snapshot. 20260526131411_cron_healthcheck_start_ping.sql replaces process_queue_with_healthcheck, but supabase/schemas/prod.sql still has the previous body with no healthcheck_start payload. Risk: the next schema regeneration/diff can look dirty or accidentally drop the start-ping change from the checked-in schema. Verification:
g process_queue_with_healthcheck supabase/schemas/prod.sql still points at the old function body.

@riderx riderx force-pushed the codex/hyperping-cron-healthcheck-start-grace branch from 98ebb63 to 679d1a4 Compare May 26, 2026 17:20
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql (1)

1-85: ⚠️ Potential issue | 🟠 Major

Update supabase/schemas/prod.sql schema snapshot for process_queue_with_healthcheck
supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql defines public.process_queue_with_healthcheck(..., healthcheck_url) with healthcheck_start logic, but supabase/schemas/prod.sql contains no matches for process_queue_with_healthcheck and no occurrences of healthcheck_start/healthcheck_start_pending—so the schema snapshot is missing this new function body (confirm/update to prevent schema drift).

🤖 Prompt for 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.

In `@supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql` around
lines 1 - 85, The schema snapshot supabase/schemas/prod.sql is missing the new
function definition for public.process_queue_with_healthcheck (and the new
healthcheck_start / healthcheck_start_pending logic), causing schema drift;
update prod.sql to include the exact CREATE OR REPLACE FUNCTION body added in
20260526131411_cron_healthcheck_start_ping.sql (the function name
process_queue_with_healthcheck, parameters queue_names, batch_size,
healthcheck_url and the healthcheck_start/healthcheck_start_pending handling and
PERFORM net.http_post calls) so the repository schema matches the migration and
then commit the updated prod.sql.
🤖 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 `@scripts/create_hyperping_cron_healthchecks.ts`:
- Around line 167-169: In getGracePeriodParts, the grace window is being rounded
up via Math.ceil which can push a capped max (e.g., 61s) past the configured
max; change the rounding to round down to the nearest minute after applying the
1-minute floor: compute cappedGraceSeconds = Math.min(maxGracePeriodSeconds,
cadenceSeconds - 1), then compute minutes = Math.floor(Math.max(60,
cappedGraceSeconds) / 60) and pass minutes*60 into getPeriodParts so the grace
never exceeds maxGracePeriodSeconds; update references in the
getGracePeriodParts function accordingly.

In `@supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql`:
- Around line 37-39: Replace the unqualified LEAST call with the fully qualified
pg_catalog.least in the expression that computes calls_needed (which uses
queue_size and batch_size and already uses pg_catalog.ceil); also scan the
surrounding function to confirm the search_path is cleared (set to empty) and
that all other PostgreSQL built-ins are fully qualified (e.g., pg_catalog.ceil)
to satisfy the migration hardening rules.

---

Outside diff comments:
In `@supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql`:
- Around line 1-85: The schema snapshot supabase/schemas/prod.sql is missing the
new function definition for public.process_queue_with_healthcheck (and the new
healthcheck_start / healthcheck_start_pending logic), causing schema drift;
update prod.sql to include the exact CREATE OR REPLACE FUNCTION body added in
20260526131411_cron_healthcheck_start_ping.sql (the function name
process_queue_with_healthcheck, parameters queue_names, batch_size,
healthcheck_url and the healthcheck_start/healthcheck_start_pending handling and
PERFORM net.http_post calls) so the repository schema matches the migration and
then commit the updated prod.sql.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d6fe4fa-98be-4ecf-aa4e-9e804e7bc262

📥 Commits

Reviewing files that changed from the base of the PR and between 98ebb63 and 679d1a4.

📒 Files selected for processing (5)
  • scripts/create_hyperping_cron_healthchecks.ts
  • supabase/functions/_backend/triggers/queue_consumer.ts
  • supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql
  • tests/create-hyperping-cron-healthchecks.unit.test.ts
  • tests/queue-consumer-message-shape.unit.test.ts

Comment thread scripts/create_hyperping_cron_healthchecks.ts Outdated
Comment thread supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql Outdated
@riderx riderx force-pushed the codex/hyperping-cron-healthcheck-start-grace branch from 679d1a4 to 0b8367d Compare May 26, 2026 17:28
@riderx riderx force-pushed the codex/hyperping-cron-healthcheck-start-grace branch from 0b8367d to 20ed71c Compare May 26, 2026 17:40
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: 2

🤖 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 `@supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql`:
- Around line 18-20: The migration currently allows a single healthcheck_url to
be shared across multiple queue_names (in cron_healthcheck_start_ping and
related logic), which can report success prematurely; update the migration so
healthcheck_url is only used when exactly one queue_name is targeted or else
nullified/rewritten per-queue. Concretely, inside cron_healthcheck_start_ping
(and the same guard area covering the logic in the 27-57 block) validate that
queue_names contains exactly one entry before assigning/forwarding
healthcheck_url, or transform healthcheck_url into a per-queue URL (e.g., append
the queue_name) so each queue has its own completion signal; use the symbols
healthcheck_url, queue_names, and queue_consumer to locate and modify the checks
and assignment logic.

In `@tests/create-hyperping-cron-healthchecks.unit.test.ts`:
- Around line 28-70: Add a new test case in the
create_hyperping_cron_healthchecks suite that covers cron-expression branches by
building a cron-style taskRow via createCronTaskRow with
run_at_minute/run_at_hour and/or run_on_dow/run_on_day set (instead of
second_/minute_/hour_interval), then call buildHealthcheckPayload and assert the
resulting candidate.payload.period_type/period_value and
candidate.payload.grace_period_value/grace_period_type match the expected
cron-schedule behavior (e.g., one-minute period for run_at_minute, correct
minutes/hours units, and proper grace rounding/capping as in other tests);
reference buildHealthcheckPayload and createCronTaskRow to locate where to add
the assertion.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 85ba8370-d6f3-401c-aec1-c67d2f12ccd9

📥 Commits

Reviewing files that changed from the base of the PR and between 679d1a4 and 20ed71c.

📒 Files selected for processing (5)
  • scripts/create_hyperping_cron_healthchecks.ts
  • supabase/functions/_backend/triggers/queue_consumer.ts
  • supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql
  • tests/create-hyperping-cron-healthchecks.unit.test.ts
  • tests/queue-consumer-message-shape.unit.test.ts

Comment thread supabase/migrations/20260526131411_cron_healthcheck_start_ping.sql Outdated
@riderx riderx force-pushed the codex/hyperping-cron-healthcheck-start-grace branch from 20ed71c to 22e93fb Compare May 26, 2026 17:54
@riderx riderx force-pushed the codex/hyperping-cron-healthcheck-start-grace branch from 22e93fb to 8402bb9 Compare May 26, 2026 21:46
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 3be2959 into main May 26, 2026
41 of 42 checks passed
@riderx riderx deleted the codex/hyperping-cron-healthcheck-start-grace branch May 26, 2026 21:52
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: 3

🤖 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 `@scripts/create_hyperping_cron_healthchecks.ts`:
- Around line 181-184: The cron row handling allows both run_on_day and
run_on_dow to be set which creates inconsistent semantics between
getCronExpression(), process_all_cron_tasks, and cadence/grace calculation in
scripts/create_hyperping_cron_healthchecks.ts; fix by choosing one approach:
either enforce mutual exclusivity at write/DB level (add validation in the
create/update path or DB check constraint to ensure exactly one of run_on_day or
run_on_dow is non-null) or change the cron/grace generation to treat both fields
as combined (AND) everywhere—update getCronExpression(), process_all_cron_tasks,
and the cadence/grace logic in create_hyperping_cron_healthchecks.ts to use the
same AND semantics so schedules match the scheduler.

In `@supabase/functions/_backend/triggers/queue_consumer.ts`:
- Around line 967-968: The startup healthcheck ping is currently sent after
acquiring the DB client so if getPgClient(c) throws we never call
maybePingCronHealthcheckStart; move the call to
maybePingCronHealthcheckStart(healthcheckUrl) to occur before any DB setup in
the background task (i.e., before the getPgClient(c) invocation and related DB
initialization code) so the /start ping is sent even on init failures; ensure
you still guard the call with healthcheckUrl !== null and await the ping before
proceeding to create the client.

In `@tests/create-hyperping-cron-healthchecks.unit.test.ts`:
- Around line 28-81: The tests never exercise the weekly/monthly branches in
getScheduledCadenceSeconds, so add unit cases that call buildHealthcheckPayload
with createCronTaskRow rows setting run_on_dow (weekly) and run_on_day (monthly)
and assert the generated payload.period_type/period_value and
payload.grace_period_type/pretty_value (e.g., weekly should produce days/hours
as expected, monthly should produce days/weeks or hours per your function
logic); specifically add one test that sets run_on_dow (e.g., [1] or 0-based
weekday) and verifies cadence seconds and derived grace, and another that sets
run_on_day (e.g., 15) and verifies monthly cadence and grace derived by
getScheduledCadenceSeconds.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 74b9c575-d7ed-4ccc-86d8-dcaf87d2dd5b

📥 Commits

Reviewing files that changed from the base of the PR and between 20ed71c and 8402bb9.

📒 Files selected for processing (4)
  • scripts/create_hyperping_cron_healthchecks.ts
  • supabase/functions/_backend/triggers/queue_consumer.ts
  • tests/create-hyperping-cron-healthchecks.unit.test.ts
  • tests/queue-consumer-message-shape.unit.test.ts

Comment on lines +181 to +184
if (row.run_on_day !== null)
return 28 * 24 * 60 * 60
if (row.run_on_dow !== null)
return 7 * 24 * 60 * 60
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo 'Searching for cron_tasks constraints and validators around run_on_day/run_on_dow...'
rg -n -C3 "cron_tasks|run_on_day|run_on_dow" supabase scripts tests

Repository: Cap-go/capgo

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo 'Searching for constraints/checks enforcing mutual exclusivity between run_on_day and run_on_dow...'
rg -n --no-heading --stats --hidden \
  'run_on_day\s+IS\s+NULL|run_on_dow\s+IS\s+NULL|run_on_day.*run_on_dow|run_on_dow.*run_on_day|CHECK\s*\(|constraint.*cron_tasks|ALTER TABLE.*cron_tasks' \
  supabase/schemas supabase/migrations | head -n 200

echo 'Searching for any application-layer validation around run_on_day/run_on_dow...'
rg -n --no-heading --stats 'run_on_day|run_on_dow' scripts src supabase | head -n 200

Repository: Cap-go/capgo

Length of output: 27157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo 'Searching for constraint/policy definitions mentioning both run_on_day and run_on_dow...'
rg -n --no-heading 'cron_tasks.*run_on_(day|dow)|run_on_(day|dow).*cron_tasks|run_on_day.*run_on_dow|run_on_dow.*run_on_day|CHECK\s*\(.*run_on_day.*run_on_dow|CHECK\s*\(.*run_on_dow.*run_on_day|constraint.*cron_tasks.*run_on_day|constraint.*cron_tasks.*run_on_dow' \
  supabase/schemas/prod.sql supabase/migrations | head -n 200

echo 'Inspecting the cron_tasks table definition block in supabase/schemas/prod.sql...'
python3 - <<'PY'
import re, itertools, pathlib
p = pathlib.Path("supabase/schemas/prod.sql")
text = p.read_text(encoding="utf-8", errors="ignore")
m = re.search(r'CREATE TABLE IF NOT EXISTS "public"\."cron_tasks" \((.*?)\);\s*', text, re.S)
if not m:
    print("cron_tasks CREATE TABLE block not found")
    raise SystemExit(0)
block = m.group(1)
lines = block.splitlines()
print(f"cron_tasks CREATE TABLE block lines: {len(lines)}")
for i,line in enumerate(lines,1):
    if "run_on_dow" in line or "run_on_day" in line or "CHECK" in line or "CONSTRAINT" in line:
        print(f"{i:4d}:{line}")
PY

echo 'Searching for any application code that rejects both run_on_day and run_on_dow at once...'
rg -n --no-heading 'run_on_(day|dow).*non|null|non|null.*run_on_(day|dow)|validate.*run_on_(day|dow)|if\s*\(.*run_on_day.*\)\s*\{|\brun_on_day\b.*\brun_on_dow\b|run_on_day.*run_on_dow' \
  scripts src supabase/functions | head -n 200

Repository: Cap-go/capgo

Length of output: 145


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="scripts/create_hyperping_cron_healthchecks.ts"
echo "=== Snippet around cadence + cron expression helpers in $FILE ==="
sed -n '150,260p' "$FILE"

echo "=== Snippet around getCronExpression emission in $FILE ==="
sed -n '260,380p' "$FILE"

echo "=== Snippet around getScheduledCadenceSeconds in $FILE ==="
sed -n '160,220p' "$FILE"

echo "=== Snippet around cron runner logic (process_all_cron_tasks) in supabase/migrations/20260521210531_cron_hyperping_healthchecks.sql ==="
sed -n '100,190p' supabase/migrations/20260521210531_cron_hyperping_healthchecks.sql

Repository: Cap-go/capgo

Length of output: 12369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# show cron_tasks create table + nearby constraints
sed -n '16060,16220p' supabase/schemas/prod.sql | cat -n | head -n 200
sed -n '16220,16380p' supabase/schemas/prod.sql | cat -n | head -n 200

echo '--- constraints/policies related to cron_tasks in prod.sql ---'
rg -n --no-heading 'ADD CONSTRAINT.*cron_tasks|CONSTRAINT.*cron_tasks|cron_tasks.*CHECK|CHECK.*cron_tasks|CREATE POLICY.*cron_tasks|ALTER TABLE.*cron_tasks' supabase/schemas/prod.sql | head -n 200

echo '--- lines in prod.sql containing run_on_day/run_on_dow that mention constraints/combined logic ---'
rg -n --no-heading 'run_on_day.*run_on_dow|run_on_dow.*run_on_day|run_on_day.*IS NOT NULL.*run_on_dow|run_on_dow.*IS NOT NULL.*run_on_day|CHECK.*run_on_day|CHECK.*run_on_dow' supabase/schemas/prod.sql | head -n 200

Repository: Cap-go/capgo

Length of output: 12574


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo 'Checking migrations for any CHECK/constraint mentioning run_on_day or run_on_dow...'
rg -n --no-heading 'CONSTRAINT.*run_on_(day|dow)|CHECK\s*\(.*run_on_(day|dow)|ALTER TABLE.*cron_tasks.*run_on_(day|dow)' supabase/migrations | head -n 200

echo 'Checking backend/app code for any validation preventing both run_on_day/run_on_dow...'
rg -n --no-heading 'run_on_(day|dow)' supabase/functions | head -n 200

echo 'Checking client/app for any cron_tasks insert/update logic...' 
rg -n --no-heading 'cron_tasks.*(insert|update|upsert|modify)|from\(\s*[\x27"]cron_tasks[\x27"]\s*\)' scripts src supabase/functions | head -n 200

Repository: Cap-go/capgo

Length of output: 140


🏁 Script executed:

#!/bin/bash
set -u

echo 'Migrations: CHECK/constraint mentioning run_on_day or run_on_dow'
rg -n --no-heading 'CONSTRAINT.*run_on_(day|dow)|CHECK\s*\(.*run_on_(day|dow)|ALTER TABLE.*cron_tasks.*run_on_(day|dow)' supabase/migrations || true

echo 'Backend/functions: any validation/logic around run_on_day/run_on_dow'
rg -n --no-heading 'run_on_(day|dow)' supabase/functions || true

echo 'App scripts/types: any validation/logic around run_on_day/run_on_dow'
rg -n --no-heading 'run_on_(day|dow)' scripts src || true

echo 'Schemas: any constraint mentioning run_on_day/run_on_dow'
rg -n --no-heading 'CONSTRAINT.*run_on_(day|dow)|CHECK\s*\(.*run_on_(day|dow)|ALTER TABLE.*cron_tasks.*run_on_(day|dow)' supabase/schemas || true

Repository: Cap-go/capgo

Length of output: 2010


🌐 Web query:

Hyperping cron expression day of month vs day of week semantics OR AND

💡 Result:

In standard Linux cron implementations, the relationship between the day-of-month and day-of-week fields is governed by OR semantics [1][2][3]. If both fields are "restricted" (i.e., they contain values other than the wildcard *), the cron job will trigger whenever the current time matches either the specified day-of-month OR the specified day-of-week [2][4][5]. For example, an expression like 0 8 15 * 1 will trigger at 8:00 AM on the 15th of the month, AND at 8:00 AM every Monday, rather than only on Mondays that happen to fall on the 15th [1][5]. This behavior differs significantly from other cron systems, such as Quartz Scheduler or AWS EventBridge, which often do not allow both fields to be specified simultaneously (typically requiring a ? placeholder in one of the fields) to avoid this ambiguity [6][1]. While Hyperping provides a cron expression generator [7][8], its core cron scheduling behavior typically relies on the underlying system's implementation (often standard Linux cron or a library-based equivalent) [9][10]. If you require AND logic (i.e., "the first Monday of every month"), you cannot achieve this with a single standard cron expression [1]. Instead, it is common practice to use a wrapper or "shell guard" to filter execution [1]: 0 8 1-7 * 1 [ $(date +%u) -eq 1 ] && /your/command This executes the job daily between the 1st and 7th, but only proceeds if the current day of the week is Monday [1].

Citations:


Add/require mutual exclusivity for cron_tasks.run_on_day vs cron_tasks.run_on_dow (or align semantics).

  • public.cron_tasks defines both run_on_day and run_on_dow as nullable with no CHECK/constraint preventing both from being set.
  • scripts/create_hyperping_cron_healthchecks.ts derives cadence/grace using run_on_day first, and getCronExpression() includes both fields when both are non-null.
  • process_all_cron_tasks applies both day constraints together (AND), while standard cron evaluation of day-of-month/day-of-week is typically OR—so mixed rows can make Hyperping’s expected schedule diverge from actual execution.

Fix by enforcing “only one of run_on_day / run_on_dow” at the DB/write path, or by changing the cron/grace generation to match the scheduler’s AND behavior.

🤖 Prompt for 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.

In `@scripts/create_hyperping_cron_healthchecks.ts` around lines 181 - 184, The
cron row handling allows both run_on_day and run_on_dow to be set which creates
inconsistent semantics between getCronExpression(), process_all_cron_tasks, and
cadence/grace calculation in scripts/create_hyperping_cron_healthchecks.ts; fix
by choosing one approach: either enforce mutual exclusivity at write/DB level
(add validation in the create/update path or DB check constraint to ensure
exactly one of run_on_day or run_on_dow is non-null) or change the cron/grace
generation to treat both fields as combined (AND) everywhere—update
getCronExpression(), process_all_cron_tasks, and the cadence/grace logic in
create_hyperping_cron_healthchecks.ts to use the same AND semantics so schedules
match the scheduler.

Comment on lines +967 to +968
if (healthcheckUrl !== null)
await maybePingCronHealthcheckStart(healthcheckUrl)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Send /start ping before DB client acquisition to cover init failures.

If getPgClient(c) throws, the run fails without ever reporting /start, which can keep healthchecks green for startup-path failures. Move maybePingCronHealthcheckStart before DB setup in the background task.

Suggested fix
   await backgroundTask(c, (async () => {
     cloudlog({ requestId: c.get('requestId'), message: `[Background Queue Sync] Starting background execution for queue: ${queueName} with batch size: ${finalBatchSize}` })
     let db: ReturnType<typeof getPgClient> | null = null
     try {
-      db = getPgClient(c)
       if (healthcheckUrl !== null)
         await maybePingCronHealthcheckStart(healthcheckUrl)
+      db = getPgClient(c)
       const result = await processQueue(c, db, queueName, finalBatchSize)
       await maybePingCronHealthcheck(db, queueName, result, healthcheckUrl)
🤖 Prompt for 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.

In `@supabase/functions/_backend/triggers/queue_consumer.ts` around lines 967 -
968, The startup healthcheck ping is currently sent after acquiring the DB
client so if getPgClient(c) throws we never call maybePingCronHealthcheckStart;
move the call to maybePingCronHealthcheckStart(healthcheckUrl) to occur before
any DB setup in the background task (i.e., before the getPgClient(c) invocation
and related DB initialization code) so the /start ping is sent even on init
failures; ensure you still guard the call with healthcheckUrl !== null and await
the ping before proceeding to create the client.

Comment on lines +28 to +81
describe('create_hyperping_cron_healthchecks', () => {
it.concurrent('uses second-based periods and floors grace to one minute', () => {
const candidate = buildHealthcheckPayload(createCronTaskRow({
second_interval: 10,
}), 10 * 60, 'UTC')

expect(candidate.payload.period_value).toBe(10)
expect(candidate.payload.period_type).toBe('seconds')
expect(candidate.payload.grace_period_value).toBe(1)
expect(candidate.payload.grace_period_type).toBe('minutes')
})

it.concurrent('uses one-minute grace for one-minute cron tasks', () => {
const candidate = buildHealthcheckPayload(createCronTaskRow({
minute_interval: 1,
}), 10 * 60, 'UTC')

expect(candidate.payload.period_value).toBe(1)
expect(candidate.payload.period_type).toBe('minutes')
expect(candidate.payload.grace_period_value).toBe(1)
expect(candidate.payload.grace_period_type).toBe('minutes')
})

it.concurrent('keeps long-schedule grace at the configured maximum', () => {
const candidate = buildHealthcheckPayload(createCronTaskRow({
hour_interval: 2,
}), 10 * 60, 'UTC')

expect(candidate.payload.period_value).toBe(2)
expect(candidate.payload.period_type).toBe('hours')
expect(candidate.payload.grace_period_value).toBe(10)
expect(candidate.payload.grace_period_type).toBe('minutes')
})

it.concurrent('rounds grace down so it does not exceed the configured maximum', () => {
const candidate = buildHealthcheckPayload(createCronTaskRow({
hour_interval: 2,
}), 61, 'UTC')

expect(candidate.payload.grace_period_value).toBe(1)
expect(candidate.payload.grace_period_type).toBe('minutes')
})

it.concurrent('derives grace for daily cron schedules', () => {
const candidate = buildHealthcheckPayload(createCronTaskRow({
run_at_hour: 3,
run_at_minute: 15,
}), 2 * 60 * 60, 'UTC')

expect(candidate.payload.cron).toBe('15 3 * * *')
expect(candidate.payload.grace_period_value).toBe(2)
expect(candidate.payload.grace_period_type).toBe('hours')
})
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add explicit weekly/monthly cadence cases.

This suite never hits the new run_on_dow and run_on_day branches in getScheduledCadenceSeconds(), so regressions in weekly/monthly grace derivation would still pass.

🧪 Example additions
 describe('create_hyperping_cron_healthchecks', () => {
+  it.concurrent('derives grace for weekly cron schedules', () => {
+    const candidate = buildHealthcheckPayload(createCronTaskRow({
+      run_at_hour: 3,
+      run_at_minute: 15,
+      run_on_dow: 1,
+    }), 2 * 60 * 60, 'UTC')
+
+    expect(candidate.payload.cron).toBe('15 3 * * 1')
+    expect(candidate.payload.grace_period_value).toBe(2)
+    expect(candidate.payload.grace_period_type).toBe('hours')
+  })
+
+  it.concurrent('derives grace for monthly cron schedules', () => {
+    const candidate = buildHealthcheckPayload(createCronTaskRow({
+      run_at_hour: 3,
+      run_at_minute: 15,
+      run_on_day: 1,
+    }), 2 * 60 * 60, 'UTC')
+
+    expect(candidate.payload.cron).toBe('15 3 1 * *')
+    expect(candidate.payload.grace_period_value).toBe(2)
+    expect(candidate.payload.grace_period_type).toBe('hours')
+  })
+
   it.concurrent('uses second-based periods and floors grace to one minute', () => {
🤖 Prompt for 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.

In `@tests/create-hyperping-cron-healthchecks.unit.test.ts` around lines 28 - 81,
The tests never exercise the weekly/monthly branches in
getScheduledCadenceSeconds, so add unit cases that call buildHealthcheckPayload
with createCronTaskRow rows setting run_on_dow (weekly) and run_on_day (monthly)
and assert the generated payload.period_type/period_value and
payload.grace_period_type/pretty_value (e.g., weekly should produce days/hours
as expected, monthly should produce days/weeks or hours per your function
logic); specifically add one test that sets run_on_dow (e.g., [1] or 0-based
weekday) and verifies cadence seconds and derived grace, and another that sets
run_on_day (e.g., 15) and verifies monthly cadence and grace derived by
getScheduledCadenceSeconds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants