feat(attio): add attribute tools + fix API alignment gaps#5445
Conversation
- Add 4 new tools: attio_list_attributes, get_attribute, create_attribute, update_attribute - Add missing completed_at field to task tools - Fix note tags output to match Attio's actual response shape (workspace-member vs record tags) - Fix attribute outputs missing is_default_value_enabled, default_value, relationship - Fix create_comment sending both entry and thread_id (Attio requires exactly one) - Wire attribute pagination + task sort into the block
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Comment creation now picks exactly one target via a Comment On control (list entry, record, or thread reply) and builds a mutually exclusive API body; legacy blocks without Partial-update UX splits create vs update dropdowns for task completion, list workspace access, and attribute flags, using Leave unchanged so PATCH-style updates do not overwrite fields unintentionally. The block Output and API fixes: task tools expose Reviewed by Cursor Bugbot for commit 43f1662. Configure here. |
Greptile SummaryThis PR adds four new attribute schema management tools (
Confidence Score: 4/5Safe to merge for the new attribute tools and comment/block fixes; the task tools carry a pre-existing silent-substitution issue (malformed JSON silently resets linked_records/assignees to []) that was flagged in a prior review cycle but not addressed in this PR. The new attribute tools, comment refactor, and block wiring cleanup are all correct and well-guarded. The unresolved concern is in create_task.ts and update_task.ts: a malformed linkedRecords or assignees JSON string silently substitutes [], so a PATCH can wipe existing task associations without surfacing an error. Every other tool touched in this PR (create_record, update_record, assert_record, create_attribute, update_attribute) now throws on bad JSON, making the task tools the remaining outlier. apps/sim/tools/attio/create_task.ts and apps/sim/tools/attio/update_task.ts — the silent JSON catch still substitutes [] instead of throwing, inconsistent with how the rest of the Attio tools were fixed in this PR. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[create_comment called] --> B{commentTarget?}
B -->|undefined| C{commentThreadId set?}
C -->|yes| D[target = 'thread']
C -->|no| E[target = 'entry']
B -->|'entry'| E
B -->|'record'| F[target = 'record']
B -->|'thread'| D
E --> G{list + entryId set?}
G -->|yes| H[body.entry = list + entry_id]
G -->|no| I[throw: must provide entry fields]
F --> J{recordObject + recordId set?}
J -->|yes| K[body.record = object + record_id]
J -->|no| I
D --> L{threadId set?}
L -->|yes| M[body.thread_id = threadId]
L -->|no| I
H --> N[POST /v2/comments]
K --> N
M --> N
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[create_comment called] --> B{commentTarget?}
B -->|undefined| C{commentThreadId set?}
C -->|yes| D[target = 'thread']
C -->|no| E[target = 'entry']
B -->|'entry'| E
B -->|'record'| F[target = 'record']
B -->|'thread'| D
E --> G{list + entryId set?}
G -->|yes| H[body.entry = list + entry_id]
G -->|no| I[throw: must provide entry fields]
F --> J{recordObject + recordId set?}
J -->|yes| K[body.record = object + record_id]
J -->|no| I
D --> L{threadId set?}
L -->|yes| M[body.thread_id = threadId]
L -->|no| I
H --> N[POST /v2/comments]
K --> N
M --> N
Reviews (13): Last reviewed commit: "fix(attio): preserve legacy taskIsComple..." | Re-trigger Greptile |
- Throw on invalid config JSON instead of silently overwriting with {}
(create_attribute, update_attribute)
- create_attribute: omit config field entirely when not provided instead
of always sending {}
- Add "Leave unchanged" option to Required/Unique dropdowns so
update_attribute no longer clears existing constraints on unrelated
field updates
- Fix stale sort param description on list_tasks (was missing
completed_at:asc/desc variants)
|
Addressed round-1 feedback in a87e0fa:
|
…exclusion
- create_comment: support all 3 of Attio's mutually-exclusive comment
targets (thread_id, record, entry), not just thread_id/entry
- Fix update_task silently clearing is_completed on unrelated field
updates (taskIsCompletedUpdate now defaults to "leave unchanged")
- Fix update_list silently resetting workspace_access to full-access on
unrelated field updates (listWorkspaceAccessUpdate, same pattern)
- create_attribute: restore config:{} as always-required per Attio's
schema (previously omitted it entirely, which is invalid on create)
- create_record/update_record/assert_record/list_records: throw on
invalid values/filter/sorts JSON instead of silently substituting {}
(was a silent data-loss risk on malformed input)
- get_task: drop stray Content-Type header on a GET request
- types.ts: remove two dead unreferenced interfaces, fix
workspaceMemberAccess type (was string, is actually an array)
|
Pushed a second round of fixes (55c63b7) from an independent parallel re-validation pass — fixed 2 more silent-clobber bugs on update operations (task completion status, list workspace access being reset to defaults on unrelated edits), completed the create_comment 3-way mutual exclusion (thread_id/record/entry per Attio's actual schema), fixed silent JSON-parse-to-empty-object fallbacks on record create/update/assert (was a data-loss risk), and restored the required config:{} default on attribute creation per Attio's OpenAPI spec. |
|
@greptile review |
|
@cursor review |
Params like taskIsCompleted/listWorkspaceAccess/attributeIsMultiselect/ attributeIsArchived are only meant for one specific operation, but their mapping wasn't checking params.operation. A stale value persisted in block state from a prior operation selection (e.g. switching the dropdown from create_task to update_task) could leak through and override the "leave unchanged" default on the other operation.
|
Fixed the 2 new High-severity Cursor findings from the 55c63b7 review (stale taskIsCompleted/listWorkspaceAccess param leakage across operation switches) in 4245f3a — see inline replies. Also audited every other operation-scoped default in the block for the same class of issue and gated attributeIsMultiselect/attributeIsArchived defensively. |
|
@cursor review |
…-state - Add "Leave unchanged" default to attributeIsArchived dropdown, same pattern as isRequired/isUnique, so a stale archived flag from an earlier edit can't unarchive an attribute on an unrelated update - create_comment: replace field-presence inference (which let stale record fields hijack a list-entry comment or vice versa) with an explicit commentTarget selector (List Entry / Record / Reply to Thread). Only the fields for the selected target are ever forwarded
|
Fixed both High-severity findings from the last Cursor pass in 7b23b14 — attributeIsArchived now has a Leave-unchanged default, and create_comment now uses an explicit commentTarget selector instead of field-presence inference. See inline replies. |
|
@cursor review |
…ack-compat - taskFilterCompleted (list_tasks filter) was mapped to isCompleted for every operation; gate it to list_tasks so it can't override the isCompleted value on an unrelated update_task call - Generic threadId (get_thread) was unconditionally forwarded and create_comment prioritizes thread_id first, so a leftover threadId from configuring get_thread could silently hijack a comment meant for a list entry or record; gate it to get_thread - Default commentTarget to the pre-existing behavior (entry, or thread if commentThreadId is set) when absent, so blocks saved before this field existed keep working instead of throwing
|
Fixed all 3 findings from the last Cursor pass in c0fe19b — taskFilterCompleted and the generic threadId field are now gated to their originating operations (list_tasks / get_thread), and commentTarget falls back to the pre-existing behavior when absent so previously-saved blocks don't break. See inline replies. |
|
@cursor review |
…e-value class of bugs The params() function reuses cleanParams keys (title, content, apiSlug, filter, sorts, recordId, entryId, list, object, threadId, ...) across several unrelated operation families. Every mapping was unconditional on presence alone, so a stale value left in block state from a previously selected operation could silently leak into an unrelated request and overwrite the intended value (last-write-wins on a shared key). Rewrote the whole function to gate every line by params.operation against the exact operation set its subBlock's condition exposes it under, closing this entire bug class in one pass instead of patching individual instances as they were found in review. Also split attributeIsRequired/attributeIsUnique into create-only (default false) and update-only (tri-state, default "leave unchanged") variants — the shared field let an explicit Yes/No choice from create_attribute carry over and silently change constraints on an unrelated update_attribute call.
|
Given the recurring pattern of stale-value findings across multiple rounds (task, list, attribute, comment fields), I rewrote the entire tools.config.params function in ba5132c to gate every single cleanParams mapping by params.operation against the exact operation set from that field's own subBlock condition — not just the two fields flagged this round. This closes the whole class of cross-operation param leakage in one pass rather than continuing to patch individual instances. |
|
@cursor review |
…date taskIsCompleted and listWorkspaceAccess pre-date this PR as fields shared between create and update operations (confirmed present on origin/staging). Splitting them into create/update variants earlier this session meant existing saved blocks with a value stored under the legacy field name would silently stop applying it on update_task / update_list once the new -Update field (always undefined for old blocks) took over. Fall back to the legacy field when the new field is untouched, so old saved workflows keep behaving exactly as before.
|
Fixed the legacy-field back-compat regression in 43f1662 — taskIsCompleted/listWorkspaceAccess predate this PR and needed a fallback path for pre-existing saved blocks. See inline reply. |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 43f1662. Configure here.
|
Fixed in d2f5230 — create_task/update_task now throw on invalid linkedRecords/assignees JSON instead of silently substituting []. While auditing for this exact pattern, I also proactively found and fixed 6 more instances of the same silent-parse-to-empty-value bug that hadn't been flagged yet: create_list_entry/update_list_entry (entryValues), create_webhook/update_webhook (subscriptions), and query_list_entries (filter, sorts). All now throw a clear error instead of silently dropping user data. |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 43f1662. Configure here.
|
Fixed the last open item — create_comment's tool description now reflects all 3 supported targets (list entry / record / thread reply). Combined with the 7 JSON-parse fixes from d2f5230, this should close everything flagged so far. |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 43f1662. Configure here.
|
Noting for the record: this exact finding was already fixed in d2f5230 (create_task.ts/update_task.ts now throw |
Summary
attio_list_attributes,get_attribute,create_attribute,update_attributecompleted_atoutput on task toolstagsoutput to match Attio's real response shape (workspace-member vs record tag variants)is_default_value_enabled,default_value,relationshipfieldscreate_commentsending bothentryandthread_idin the same request (Attio requires exactly one)Type of Change
Testing
Tested manually. Ran full validate-integration audit against Attio's live REST API docs across 5 independent passes (records/objects/attributes, lists/list-entries, tasks/notes/comments/threads, members/webhooks/block-wiring, backwards-compat diff). Confirmed change is purely additive — no existing tool ID, param, or output field was removed or renamed.
Checklist