Skip to content

fix(queue): skip scheduling HUBSPOT_SYNC when HUBSPOT_API_KEY is unset - #291

Merged
jpr5 merged 1 commit into
mainfrom
fix/worker-skip-hubspot-sync-without-key
Oct 28, 2026
Merged

jpr5 merged 1 commit into
mainfrom
fix/worker-skip-hubspot-sync-without-key

Conversation

@jpr5

@jpr5 jpr5 commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

What

The worker scheduled HUBSPOT_SYNC every day even though prod has no HUBSPOT_API_KEY, so every run failed at HubSpot client construction and dead-lettered (prod worker logs, last 24h). Per Nathan, the sync is off for now.

resolveScheduledJobs() in packages/outpost/queue/src/scheduler.ts drops HUBSPOT_SYNC from the default schedule when HUBSPOT_API_KEY is unset, empty or whitespace, and logs one startup line:

[Scheduler] Skipping HUBSPOT_SYNC: HUBSPOT_API_KEY is not set (set it to enable the HubSpot sync)

With a key set the schedule is unchanged, so the sync comes back on the next worker start after a key is added.

Why the scheduler, not the job handler

A guard in the handler would still create a job row every day and mark it done or failed. In the scheduler, no row is created, and the Starting with N scheduled jobs count is correct. new Scheduler(definitions) with explicit definitions is not affected. The HubSpot client, the handler, the web sync route and the other jobs are not changed.

Local red-green proof (real Scheduler from queue/dist, local Postgres pgvector:pg16, migrations applied)

The harness builds the package, clears job, runs new Scheduler().start() for 3s, stops it, and lists the job rows it created. In the RED run it also passes the HUBSPOT_SYNC row to the real handleHubSpotSync. With the dummy key, it stops at scheduling and makes no HubSpot call. The key value is redacted.

RED (before the fix, key unset)

=== RED (pre-fix, key unset) 11:35 AM PDT
HUBSPOT_API_KEY=<unset>
[Scheduler] Starting with 7 scheduled jobs
[Scheduler] Created SLA_CHECK job cmvsgg0xo00002lagan0uqfd8: Check SLA compliance for all open tickets
[Scheduler] Created GITHUB_REACTION_POLL job cmvsgg0xu00012laguob921qv: Poll GitHub reactions on AI-authored comments for feedback signal
[Scheduler] Created JOB_CLEANUP job cmvsgg0xx00022laglpaaebab: Clean up old completed/dead-letter jobs and sync events
[Scheduler] Created PENDING_RESPONSE_SWEEP job cmvsgg0y300032lagm8xmq9dg: Settle primary AI responses stranded in PENDING with no live owning job
[Scheduler] Created ACCOUNT_SCORING job cmvsgg0y400052lagq8s423d2: Run sentiment and engagement scoring on all accounts
[Scheduler] Created ONBOARDING_DIGEST job cmvsgg0y300042lagzwmgayxc: Compile daily new member onboarding digest
[Scheduler] Created HUBSPOT_SYNC job cmvsgg0y400062lagqhqavsle: Sync accounts from HubSpot CRM
[Scheduler] Stopped
job types created: ACCOUNT_SCORING, GITHUB_REACTION_POLL, HUBSPOT_SYNC, JOB_CLEANUP, ONBOARDING_DIGEST, PENDING_RESPONSE_SWEEP, SLA_CHECK
HUBSPOT_SYNC rows: 1
handler result: {"success":false,"error":"HubSpot client initialization failed: HubSpot API key is required. Set HUBSPOT_API_KEY env var or pass it to the constructor."}

GREEN (after the fix)

=== GREEN (post-fix) 11:36 AM PDT
--- key unset
HUBSPOT_API_KEY=<unset>
[Scheduler] Skipping HUBSPOT_SYNC: HUBSPOT_API_KEY is not set (set it to enable the HubSpot sync)
[Scheduler] Starting with 6 scheduled jobs
[Scheduler] Created SLA_CHECK job cmvsgh13900002lpnyh6lmtqp: Check SLA compliance for all open tickets
[Scheduler] Created GITHUB_REACTION_POLL job cmvsgh13d00012lpn4qbb04t6: Poll GitHub reactions on AI-authored comments for feedback signal
[Scheduler] Created ACCOUNT_SCORING job cmvsgh13k00022lpnmuj1pwrn: Run sentiment and engagement scoring on all accounts
[Scheduler] Created JOB_CLEANUP job cmvsgh13k00042lpnvu0zvo5d: Clean up old completed/dead-letter jobs and sync events
[Scheduler] Created ONBOARDING_DIGEST job cmvsgh13k00032lpnhmisu3v9: Compile daily new member onboarding digest
[Scheduler] Created PENDING_RESPONSE_SWEEP job cmvsgh13p00052lpn9qaogc5a: Settle primary AI responses stranded in PENDING with no live owning job
[Scheduler] Stopped
job types created: ACCOUNT_SCORING, GITHUB_REACTION_POLL, JOB_CLEANUP, ONBOARDING_DIGEST, PENDING_RESPONSE_SWEEP, SLA_CHECK
HUBSPOT_SYNC rows: 0
exit=0
--- key empty
HUBSPOT_API_KEY=<empty>
[Scheduler] Skipping HUBSPOT_SYNC: HUBSPOT_API_KEY is not set (set it to enable the HubSpot sync)
[Scheduler] Starting with 6 scheduled jobs
[Scheduler] Created ONBOARDING_DIGEST job cmvsgh3ib00002lv4x7zmfaar: Compile daily new member onboarding digest
[Scheduler] Created PENDING_RESPONSE_SWEEP job cmvsgh3ih00012lv4ck53jrbp: Settle primary AI responses stranded in PENDING with no live owning job
[Scheduler] Created ACCOUNT_SCORING job cmvsgh3ir00022lv4ls946c21: Run sentiment and engagement scoring on all accounts
[Scheduler] Created SLA_CHECK job cmvsgh3ir00042lv4tyvv6jx8: Check SLA compliance for all open tickets
[Scheduler] Created GITHUB_REACTION_POLL job cmvsgh3ir00032lv4q6awhnta: Poll GitHub reactions on AI-authored comments for feedback signal
[Scheduler] Created JOB_CLEANUP job cmvsgh3it00052lv4z8i44ikx: Clean up old completed/dead-letter jobs and sync events
[Scheduler] Stopped
job types created: ACCOUNT_SCORING, GITHUB_REACTION_POLL, JOB_CLEANUP, ONBOARDING_DIGEST, PENDING_RESPONSE_SWEEP, SLA_CHECK
HUBSPOT_SYNC rows: 0
exit=0
--- dummy key set (stop at scheduling; handler not run)
HUBSPOT_API_KEY=<set:redacted>
[Scheduler] Starting with 7 scheduled jobs
[Scheduler] Created SLA_CHECK job cmvsgh5ww00002lwn7el51cl5: Check SLA compliance for all open tickets
[Scheduler] Created PENDING_RESPONSE_SWEEP job cmvsgh5x200012lwn8xlov3ck: Settle primary AI responses stranded in PENDING with no live owning job
[Scheduler] Created GITHUB_REACTION_POLL job cmvsgh5x600022lwn8yg2vvid: Poll GitHub reactions on AI-authored comments for feedback signal
[Scheduler] Created ONBOARDING_DIGEST job cmvsgh5x800032lwnyj667m8r: Compile daily new member onboarding digest
[Scheduler] Created JOB_CLEANUP job cmvsgh5x800042lwn77mkvyxq: Clean up old completed/dead-letter jobs and sync events
[Scheduler] Created ACCOUNT_SCORING job cmvsgh5x800052lwnaddsjbhy: Run sentiment and engagement scoring on all accounts
[Scheduler] Created HUBSPOT_SYNC job cmvsgh5xd00062lwn1kmcx8iq: Sync accounts from HubSpot CRM
[Scheduler] Stopped
job types created: ACCOUNT_SCORING, GITHUB_REACTION_POLL, HUBSPOT_SYNC, JOB_CLEANUP, ONBOARDING_DIGEST, PENDING_RESPONSE_SWEEP, SLA_CHECK
HUBSPOT_SYNC rows: 1
exit=0

Unit tests (queue/src/__tests__/scheduler.test.ts)

  • resolveScheduledJobs: unset, empty or whitespace key drops HUBSPOT_SYNC and logs the skip line once. A set key keeps every default job and logs nothing.
  • Scheduler with default definitions (prisma and createJob mocked): no HUBSPOT_SYNC job without a key. A HUBSPOT_SYNC job with a key.

Against the old code, 5 of the new tests fail: the 4 resolveScheduledJobs cases, and "never creates a HUBSPOT_SYNC job when HUBSPOT_API_KEY is unset". The "key set" Scheduler test passes on both, because that behavior does not change. After the fix, 8/8 pass.

Local checks

  • prettier --check (changed files): clean
  • eslint packages/outpost: 0 errors. The changed files have no warnings.
  • typecheck: packages/outpost and apps/worker pass
  • vitest: packages/outpost/queue 560/560, apps/worker 84/84. Full packages/outpost: 4966/4967. The 1 failure is ai/src/classifier-backtracking.test.ts ("starts the probe child cleanly"), which fails the same way on main without this change. Local Node v26 prints a DEP0205 warning to the child stderr.

Without the key every HUBSPOT_SYNC run fails at HubSpot client
construction and dead-letters (prod worker, last 24h). The scheduler now
leaves the job out of its default schedule when the key is unset, empty
or whitespace, and logs one startup line saying so. With a key set the
schedule is unchanged, so the sync comes back once a key is added.
@jpr5
jpr5 merged commit 00dc276 into main Oct 28, 2026
3 checks passed
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