Skip to content

fix(warehouse-sources): skip HubSpot objects the grant cannot read - #95301

Merged
trunk-io[bot] merged 1 commit into
masterfrom
claude/session-515cc1
Sep 4, 2026
Merged

fix(warehouse-sources): skip HubSpot objects the grant cannot read#95301
trunk-io[bot] merged 1 commit into
masterfrom
claude/session-515cc1

Conversation

@danielcarletti

Copy link
Copy Markdown
Contributor

Problem

A HubSpot properties table fails on every sync when the portal's grant cannot read one of HubSpot's object types. Reconnecting does not fix it.

  • The table fans out over every entry in HUBSPOT_ENDPOINTS, so one unreadable object type fails the whole table.
  • _iter_pages already means to tolerate this, and its guard catches requests.HTTPError.
  • Since fix(warehouse): check HubSpot leads scope before offering or syncing it #74909 a 403 raises HubspotForbiddenError, which is not a requests.HTTPError, so the guard stopped catching it.
  • The customer is told to reconnect. A reconnect cannot grant a scope their HubSpot plan does not include.

Changes

  • The properties, pipelines and pipeline_stages tables now skip an object type the grant cannot read, and sync the rest.
  • The owners table still fails on a 403, so the customer still gets the reconnect message.
  • Owners reads a single endpoint, so skipping it would sync an empty table and report success.
  • _iter_pages takes a keyword-only skip_forbidden and catches HubspotForbiddenError. The catch covers the HubspotMissingScopeError subclass, so a scope-gated object such as leads no longer fails the table.
  • Mechanical: 403 leaves _SKIPPABLE_STATUSES. raise_for_hubspot_status intercepts a 403 before raise_for_status, so that branch could never see one.

Nothing a person sees changes apart from the sync outcome, so there are no screenshots.

How did you test this code?

The previous test patched fetch_data and raised requests.HTTPError by hand. It never ran the status mapping, which is why it stayed green through the regression. The new cases patch the HTTP session, so the real mapping runs.

  • test_a_fan_out_skips_an_object_type_the_grant_cannot_read covers four cases: properties with a plain 403, properties with the scope-gated leads 403, pipelines, and pipeline_stages. It catches a call site that misses the skip, which is the shape of this bug.
  • test_owners_fails_the_table_when_the_grant_cannot_read_it catches a change that widens the skip to single-endpoint tables.

All four fan-out cases fail against the unfixed metadata.py and pass with it, checked locally by reverting that file and re-running.

Not done: no run against a live HubSpot portal.

Automatic notifications

  • Publish to changelog?

Docs update

None.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Written by Claude Code (Opus 5). Skills invoked: /triaging-warehouse-sync-tickets, /writing-tests, /writing-pr-descriptions.

Duplicate found: #95236 fixes the same root cause in the same two files and opened a few hours earlier. It differs on one decision. It skips the 403 for every lookup table, owners included, which turns an unreadable owners grant into an empty table that reports success. This PR keeps owners failing, which is what the reporting customer's own case supports. It also covers pipelines and pipeline_stages, and patches the HTTP session rather than hand-calling raise_for_hubspot_status inside a patched fetch_data. Merge one of the two, not both.

Rejected alternative: gating the fan-out on the schemas selected for sync. It needs a database read inside the fetcher, changes what the properties table contains, and still misses an object whose scope is never requested.

Public artifact: the investigation drew on a customer support ticket and that customer's production sync logs. Nothing from either appears in this PR. No fixture is derived from customer data, and the tests use object-type names already in the source.

The properties, pipelines and pipeline_stages tables read one path per
object type, so an object the grant cannot read must not fail the whole
table. #74909 made a 403 raise HubspotForbiddenError, which is not a
requests.HTTPError, so the skip in _iter_pages stopped catching it and
one unreadable object failed every sync of the table.

Catch HubspotForbiddenError, which also covers the missing-scope
subclass, and skip only where the caller fans out. Owners reads a single
endpoint, so it keeps the 403 and asks the customer to reconnect.

The existing test mocked fetch_data and raised requests.HTTPError by
hand, so it never exercised the status mapping and stayed green through
the regression. The new cases mock the HTTP session instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@danielcarletti danielcarletti self-assigned this Sep 4, 2026
@trunk-io

trunk-io Bot commented Sep 4, 2026

Copy link
Copy Markdown

😎 Merged successfully - details.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(warehouse-sources): restore the HubS..." | Re-trigger Greptile

@trunk-io

trunk-io Bot commented Sep 4, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@danielcarletti danielcarletti added the stamphog Request AI approval (no full review) label Sep 4, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved.

Contained bug fix within the owning team's own module, backed by tests that exercise the real HTTP-to-exception mapping; no schema, API, auth, or dependency surface is touched, and no outstanding review concerns exist.

  • 👍 on the PR from greptile-apps[bot].
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 22L, 1F substantive, 81L/2F incl. docs/generated/snapshots — within ceiling
tier T1-agent / T1b-small (81L, 2F, single-area, fix)
stamphog 2.0.0b4 .stamphog/policy.yml @ 8ee4cc3 · reviewed head 8ee4cc3

@trunk-io
trunk-io Bot merged commit 46afe2c into master Sep 4, 2026
311 checks passed
@trunk-io
trunk-io Bot deleted the claude/session-515cc1 branch September 4, 2026 18:29
@deployment-status-posthog

deployment-status-posthog Bot commented Sep 4, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-09-04 19:19 UTC Run
prod-us ✅ Deployed 2026-09-04 19:36 UTC Run
prod-eu ✅ Deployed 2026-09-04 19:30 UTC Run

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

Labels

stamphog Request AI approval (no full review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant