Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(integrations): add HubSpot Leads OAuth scope; fix reconnect wiping org defaults

merged#2840CedarCopilot

CedarCopilot wants to merge 3 commits into staging from fix/hubspot-leads-scope-and-reconnect-safety

Live on prod, 0 of 2 surfaces working, 2 days leftTimeline and evidence
  1. Opened
    Sep 20, 2026, 11:13 AM
  2. Sep 20, 2026, 1:05 PM
  3. Merged
    Sep 20, 2026, 1:22 PM
  4. Live on prod
    Sep 20, 2026, 1:22 PM
  5. Observed 0 hours, 2 surfaces, 0 requests
    Sep 20, 2026, 1:22 PM
  6. Watching

    Live on prod, 0 of 2 surfaces working, 2 days left

    Sep 20, 2026, 1:22 PM
  7. Pipelines steady after this deploy
    Sep 20, 2026, 1:22 PM

Behaviors Libra is checking

When a first-connect or legacy call already contains a settings object, organization defaults fill only missing known settings and preserve existing values and unrelated settings such as fetchUnownedDeals.Not checked
prod
CRM reconnects leave metadata.settings out of the stale connection payload and atomically add only missing periodicDealSyncEnabled and externalCrmPushEnabled keys from the live database row after the connection write.Not checked
prod
Reconnecting an existing CRM connection preserves its fieldMappings, dealSyncFilter, stageRequiredFields, and customObjectConfig values, while backfilling organization defaults only for fields that have never been set.Not checked
prod
HubSpot OAuth connections request the crm.objects.leads.read and crm.objects.leads.write scopes so Lead-bound automations can discover, read, and update HubSpot Leads.Not checked
prod

Libra found 2 production surfaces on prod but could not judge any of them yet. 2 surfaces had no requests at all. Libra checks hourly for 3 days after each deploy.

Summary

  • Adds leads-read (confirmed live via a 403 without it) and leads-write (inferred) to HUBSPOT_OAUTH_SCOPES, needed for the CRM custom-object-linkage work (PR #2833) to read/write HubSpot Leads.
  • Fixes applyOrgCrmDefaults so it only fills genuinely-unset fields (fieldMappings, dealSyncFilter, stageRequiredFields, customObjectConfig, settings) instead of unconditionally overwriting them , a rep who customized their integration and then reconnects (e.g. to pick up the new Leads scope) was having those customizations silently wiped back to org defaults.
  • Both CRM-provider call sites in trpc/routes/integrations.ts (OAuth path, api_key path) now fetch the existing connection row first and pass its metadata through so the fix actually takes effect.

Why a new scope forces this fix now

Adding leads-read/leads-write means every existing HubSpot-connected rep must hit "Reconnect" to re-authorize. Before this fix, that reconnect would have reset their fieldMappings/dealSyncFilter/etc. back to org defaults , this PR ships the scope addition and the reconnect-safety fix together so the scope change can't cause that regression.

Test plan

  • provider-config.applyOrgCrmDefaults.test.ts (new, 7 tests) covering fill-only-if-unset behavior for each field
  • Existing integration route tests pass
  • Isabelle to verify a real reconnect in staging preserves an existing rep's custom field mappings post-merge

šŸ¤– Generated with Claude Code

RetriggerConfidence Score: 4/5

The PR should not merge until reconnect settings are protected from stale-snapshot overwrites and the explicit no-cast repository requirement is satisfied.

Findings

  1. P1Ā Reconnect can overwrite settings ā–¶
  2. P2Ā Unverified scope broadens consent ā–¶
  3. P2Ā Type assertions violate requirements ā–¶
Fix with agent prompt
### Issue 1
apps/server/src/services/integrations/provider-config.ts:247-259
During reconnect, this code rebuilds the complete `settings` object from an earlier database read. The later shallow JSONB upsert replaces the stored `settings` object, so if another settings update lands between the read and the upsert, reconnect writes the stale snapshot over it and silently loses the newer user configuration. Preserve or backfill nested settings atomically, or otherwise protect this read-modify-write sequence.

### Issue 2
apps/server/src/services/integrations/crm/hubspot.ts:769
`leads-write` is now requested during every HubSpot authorization even though the comment says it was inferred, has not been validated with a write, and supports only an eventual write path. If the production HubSpot app cannot grant this scope, all HubSpot connects and reconnects can fail even when Cedar only needs Lead reads. Verify the scope against the live app before requesting it
Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
/api/trpc/integrations.slack.syncConversationChannels0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
POST /api/trpc/integrations.slack.syncConversationChannels0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.

Changed files → surfaces

  • apps/server/src/trpc/routes/integrations.ts/api/trpc/integrations.slack.syncConversationChannelsPOST /api/trpc/integrations.slack.syncConversationChannels
  • apps/server/src/services/integrations/__tests__/provider-config.applyOrgCrmDefaults.test.tsno production surface mapped
  • apps/server/src/services/integrations/crm/hubspot.tsno production surface mapped
  • apps/server/src/services/integrations/provider-config.tsno production surface mapped