Problem
No background job reports its failures to anything except logs. grep -rn "captureException" src/jobs/ src/indexer/ returns zero matches, despite Sentry being installed and initialized (@sentry/nestjs, added 2026-06-19 per the tracker).
The affected jobs are the ones the protocol depends on to stay consistent with the chain:
src/indexer/indexer.service.ts, @Cron('*/60 * * * * *')
src/jobs/transaction-status-checker/transaction-status-checker.service.ts, @Cron('*/120 * * * * *')
src/jobs/loan-payment-reminder/loan-payment-reminder.service.ts, @Cron('0 9 * * *')
src/jobs/nonce-cleanup/nonce-cleanup.service.ts, @Cron(CronExpression.EVERY_HOUR)
src/jobs/supabase-keepalive/supabase-keepalive.service.ts
context/code-standards.md requires every cron to catch its own errors and log them, and they do. Logging is where it stops. On Render, a job that throws every 60 seconds produces log lines nobody is watching and no alert anywhere.
The consequences are concrete. If the indexer stalls, on-chain events stop reaching the database and the API serves stale loan and pool state indefinitely. If the transaction status checker stalls, submitted transactions never move out of pending, so users see confirmed on-chain payments stuck as unconfirmed in the app. Both fail silently today.
This is about alerting on job health, not about reconciliation logic. On-chain to database reconciliation is #80 and default detection is #71. Do not duplicate either.
Ground Rules
This issue must be solved according to StepFi's established engineering standards. Before writing any code:
- Read context/architecture-context.md in full, especially the "no BullMQ" decision. Do not reintroduce a queue library to solve this
- Read context/code-standards.md in full, especially the Background jobs section
- Read context/progress-tracker.md to understand what has already been built and why, so you do not duplicate or contradict recent work
- Read all five job services listed above to see the existing
isRunning guard and try/catch structure before changing them
- Read
src/modules/metrics/metrics.service.ts and metrics.updater.ts, since Prometheus is already wired and is the right place for job health gauges
- Read
src/indexer/indexer-status.service.ts, which already tracks indexer state and may already expose part of what is needed
Your PR will be rejected regardless of whether CI passes if it conflicts with anything in these files, introduces patterns inconsistent with what already exists, or duplicates functionality that was solved differently elsewhere in the codebase.
What To Build
- Create a shared
JobMonitorService in src/jobs/monitoring/. Do not copy Sentry calls into five separate catch blocks. Every job should report through one place.
- Expose
recordSuccess(jobName) and recordFailure(jobName, error). recordFailure calls Sentry.captureException with the job name as a tag and the consecutive failure count as context, and logs at error level.
- Track consecutive failures per job in memory. Send to Sentry on the first failure and then on an escalating interval rather than on every tick. A job failing every 60 seconds must not generate 1,440 Sentry events per day, or the alert becomes noise and gets muted, which is the same as having no alert.
- Add Prometheus gauges via the existing metrics module: last successful run timestamp per job, and consecutive failure count per job. These are what an external alert rule can actually watch.
- Wire all five job services to call
recordSuccess and recordFailure. Keep every existing isRunning guard and try/catch exactly as it is. This adds reporting, it does not restructure the jobs.
- Add a staleness check to the health module: if any job's last success is older than a configured threshold,
GET /health reports degraded. Make thresholds configurable per job through src/config/env.ts, since a 60-second indexer and a daily reminder job cannot share one threshold.
- Do not swallow anything new. If a job currently rethrows, keep that behavior.
Files To Touch
src/jobs/monitoring/job-monitor.service.ts (new)
src/jobs/monitoring/job-monitor.module.ts (new)
src/jobs/monitoring/job-monitor.service.spec.ts (new)
src/indexer/indexer.service.ts
src/jobs/transaction-status-checker/transaction-status-checker.service.ts
src/jobs/loan-payment-reminder/loan-payment-reminder.service.ts
src/jobs/nonce-cleanup/nonce-cleanup.service.ts
src/jobs/supabase-keepalive/supabase-keepalive.service.ts
src/modules/metrics/metrics.service.ts
src/modules/health/health.service.ts
src/config/env.ts
Acceptance Criteria
Mandatory Checks Before Opening PR
PRs that fail any check above will be closed without review, regardless of how much work went into them. No exceptions. This is a Grantfox-funded, quality-first project. Code that does not meet these standards will not be merged and will not be paid.
Problem
No background job reports its failures to anything except logs.
grep -rn "captureException" src/jobs/ src/indexer/returns zero matches, despite Sentry being installed and initialized (@sentry/nestjs, added 2026-06-19 per the tracker).The affected jobs are the ones the protocol depends on to stay consistent with the chain:
src/indexer/indexer.service.ts,@Cron('*/60 * * * * *')src/jobs/transaction-status-checker/transaction-status-checker.service.ts,@Cron('*/120 * * * * *')src/jobs/loan-payment-reminder/loan-payment-reminder.service.ts,@Cron('0 9 * * *')src/jobs/nonce-cleanup/nonce-cleanup.service.ts,@Cron(CronExpression.EVERY_HOUR)src/jobs/supabase-keepalive/supabase-keepalive.service.tscontext/code-standards.mdrequires every cron to catch its own errors and log them, and they do. Logging is where it stops. On Render, a job that throws every 60 seconds produces log lines nobody is watching and no alert anywhere.The consequences are concrete. If the indexer stalls, on-chain events stop reaching the database and the API serves stale loan and pool state indefinitely. If the transaction status checker stalls, submitted transactions never move out of pending, so users see confirmed on-chain payments stuck as unconfirmed in the app. Both fail silently today.
This is about alerting on job health, not about reconciliation logic. On-chain to database reconciliation is #80 and default detection is #71. Do not duplicate either.
Ground Rules
This issue must be solved according to StepFi's established engineering standards. Before writing any code:
isRunningguard and try/catch structure before changing themsrc/modules/metrics/metrics.service.tsandmetrics.updater.ts, since Prometheus is already wired and is the right place for job health gaugessrc/indexer/indexer-status.service.ts, which already tracks indexer state and may already expose part of what is neededYour PR will be rejected regardless of whether CI passes if it conflicts with anything in these files, introduces patterns inconsistent with what already exists, or duplicates functionality that was solved differently elsewhere in the codebase.
What To Build
JobMonitorServiceinsrc/jobs/monitoring/. Do not copy Sentry calls into five separate catch blocks. Every job should report through one place.recordSuccess(jobName)andrecordFailure(jobName, error).recordFailurecallsSentry.captureExceptionwith the job name as a tag and the consecutive failure count as context, and logs at error level.recordSuccessandrecordFailure. Keep every existingisRunningguard and try/catch exactly as it is. This adds reporting, it does not restructure the jobs.GET /healthreports degraded. Make thresholds configurable per job throughsrc/config/env.ts, since a 60-second indexer and a daily reminder job cannot share one threshold.Files To Touch
src/jobs/monitoring/job-monitor.service.ts(new)src/jobs/monitoring/job-monitor.module.ts(new)src/jobs/monitoring/job-monitor.service.spec.ts(new)src/indexer/indexer.service.tssrc/jobs/transaction-status-checker/transaction-status-checker.service.tssrc/jobs/loan-payment-reminder/loan-payment-reminder.service.tssrc/jobs/nonce-cleanup/nonce-cleanup.service.tssrc/jobs/supabase-keepalive/supabase-keepalive.service.tssrc/modules/metrics/metrics.service.tssrc/modules/health/health.service.tssrc/config/env.tsAcceptance Criteria
GET /healthreports degraded when a job's last success exceeds its configured thresholdisRunningguard and try/catch is preserved unchangedMandatory Checks Before Opening PR
npm run build)anytypesPRs that fail any check above will be closed without review, regardless of how much work went into them. No exceptions. This is a Grantfox-funded, quality-first project. Code that does not meet these standards will not be merged and will not be paid.