Skip to content

fix(attio-sync): use PUT to overwrite related_people so shrinks converge - #45

Open
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-attio-sync-use-put-to-overwrite-related-people-2d4b2d
Open

detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-attio-sync-use-put-to-overwrite-related-people-2d4b2d

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Bug

The Attio alumni-interactions sync never reached a fixed point for any entry whose relatedPersonEmails shrank. src/attio/sync/writeChanges.ts applied every list-entry update via PATCH /v2/lists/{list}/entries/{entry_id}, but Attio's PATCH endpoint for list entries is append-multiselect-only: supplied multiselect values are prepended and existing values are never removed. related_people is the only multiselect attribute the sync writes (scripts/attio-setup.ts sets isMultiselect: true).

When a mentor's relatedPersonEmails shrinks — a student rejected after their mentor's entry was already synced, dropped from the projection by AND s.status != 'REJECTED' in projectParticipations.ts — the removed reference could never take effect via PATCH. So the next run re-read the unchanged related_people, buildDiffPlan saw the same mismatch, and queued the identical PATCH again — re-PATCHing that entry every run indefinitely (non-convergence). The PATCH method had been chosen in the original "Add Attio sync" commit when only single-value scalar fields were in play (where append == overwrite), and was never revisited when 676a275 added the multiselect related_people field.

Fix

Route related_people through PUT (Attio's overwrite/remove-multiselect endpoint — same path and identical request-body schema as PATCH) whenever relatedPersonEmails is in changedFields. Scalar single-value fields stay on PATCH (the method established for them; Attio's PUT docs only spell out multiselect behavior, so the split keeps scalars on the well-understood method). A row changing both kinds issues both writes and is still counted as a single entriesUpdated. The record-reference value shape entryValuesFor already emits ({ target_object, target_record_id }) is accepted by both operations unchanged, so this is a pure method swap with no client-side transformation.

Testing

  • Unit tests (offline, HTTP-stubbed, npx tsx): added four tests pinning the method choice and convergence. A relatedPersonEmails-only update uses PUT with a related_people-only body; a scalar-only update still uses PATCH with related_people absent; a mixed update splits into PUT(related_people) + PATCH(scalar) and counts as one update; and a multi-cycle convergence test exercises readAttioState → buildDiffPlan → writeChanges against a stateful mock implementing Attio's documented PATCH-prepend / PUT-overwrite semantics, confirming a shrunk reference is removed on cycle 1 and the entry reaches steady state on cycle 2. All pre-existing tests continue to pass (79 assertions, 0 failures).

  • **Regression gate

  • Typecheck (npx tsc --skipLibCheck --noEmit): clean (exit 0).

  • Lint: not run to completion — ESLint is broken project-wide (@typescript-eslint/parser@3.10.1 is incompatible with typescript@5.2.2, failing on the first parse on unmodified files), and is not gated by CI.

  • Live end-to-end: could not be verified. I started a Postgres server on a free port, applied the Prisma schema, and seeded the REJECTED-student scenario; Stage B (the DB projection) confirmed the expected shrink ({extra@example.com}, with the rejected student dropped). The live sync then reached Attio at Stage C (readAttioState) and was blocked by a 401 auth_error/unauthorized — a real Attio Admin-panel API token is required and unavailable in this environment. The create-path behavior is covered offline by the existing testRelatedPeopleResolvedAtWriteTimeAndUnresolvableDropped, which passed.


Automatic Fixes PRs can be configured here.

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