feat(attio): add attribute tools + fix API alignment gaps#5445
feat(attio): add attribute tools + fix API alignment gaps#5445waleedlatif1 wants to merge 4 commits into
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 API alignment fixes: Safer partial updates: separate update dropdowns for task completion and list workspace access so unchanged fields are not sent. Stricter JSON: record assert/create/update and list-record filter/sorts throw on invalid JSON instead of silently using empty objects. Reviewed by Cursor Bugbot for commit 4245f3a. Configure here. |
Greptile SummaryThis PR adds four new Attio attribute-management tools (
Confidence Score: 4/5Safe to merge with one targeted fix: the The apps/sim/blocks/blocks/attio.ts — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
Block["Attio Block UI\n(attio.ts)"] --> OpSelect["operation dropdown\ne.g. create_comment\nupdate_attribute"]
OpSelect -->|create_comment| CC["create_comment.ts\nPOST /v2/comments"]
CC --> MutEx{"Exactly one of:\nthreadId\nrecordObject+recordId\nlist+entryId"}
MutEx -->|"threadId"| T["thread_id in body"]
MutEx -->|"record combo"| R["record in body"]
MutEx -->|"entry combo"| E["entry in body"]
MutEx -->|"none"| ERR["throw Error"]
OpSelect -->|list_attributes| LA["list_attributes.ts\nGET /v2/{target}/{id}/attributes"]
OpSelect -->|get_attribute| GA["get_attribute.ts\nGET /v2/{target}/{id}/attributes/{attr}"]
OpSelect -->|create_attribute| CA["create_attribute.ts\nPOST /v2/{target}/{id}/attributes"]
OpSelect -->|update_attribute| UA["update_attribute.ts\nPATCH /v2/{target}/{id}/attributes/{attr}"]
CA -->|config param| ParseC{"JSON.parse\nconfig"}
ParseC -->|valid| SendC["include config in body"]
ParseC -->|invalid| ThrowC["throw 'Invalid JSON for config'"]
UA -->|config param| ParseU{"JSON.parse\nconfig"}
ParseU -->|valid| SendU["include config in body"]
ParseU -->|invalid| ThrowU["throw 'Invalid JSON for config'"]
LA & GA & CA & UA --> AttrResp["ATTRIBUTE_OUTPUT_PROPERTIES\n(shared schema)"]
%%{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
Block["Attio Block UI\n(attio.ts)"] --> OpSelect["operation dropdown\ne.g. create_comment\nupdate_attribute"]
OpSelect -->|create_comment| CC["create_comment.ts\nPOST /v2/comments"]
CC --> MutEx{"Exactly one of:\nthreadId\nrecordObject+recordId\nlist+entryId"}
MutEx -->|"threadId"| T["thread_id in body"]
MutEx -->|"record combo"| R["record in body"]
MutEx -->|"entry combo"| E["entry in body"]
MutEx -->|"none"| ERR["throw Error"]
OpSelect -->|list_attributes| LA["list_attributes.ts\nGET /v2/{target}/{id}/attributes"]
OpSelect -->|get_attribute| GA["get_attribute.ts\nGET /v2/{target}/{id}/attributes/{attr}"]
OpSelect -->|create_attribute| CA["create_attribute.ts\nPOST /v2/{target}/{id}/attributes"]
OpSelect -->|update_attribute| UA["update_attribute.ts\nPATCH /v2/{target}/{id}/attributes/{attr}"]
CA -->|config param| ParseC{"JSON.parse\nconfig"}
ParseC -->|valid| SendC["include config in body"]
ParseC -->|invalid| ThrowC["throw 'Invalid JSON for config'"]
UA -->|config param| ParseU{"JSON.parse\nconfig"}
ParseU -->|valid| SendU["include config in body"]
ParseU -->|invalid| ThrowU["throw 'Invalid JSON for config'"]
LA & GA & CA & UA --> AttrResp["ATTRIBUTE_OUTPUT_PROPERTIES\n(shared schema)"]
Reviews (6): Last reviewed commit: "fix(attio): gate operation-specific para..." | 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 |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4245f3a. Configure here.
| { label: 'Yes', id: 'true' }, | ||
| ], | ||
| condition: { field: 'operation', value: 'update_attribute' }, | ||
| mode: 'advanced', |
There was a problem hiding this comment.
Archived dropdown clobbers attribute state
High Severity
The update_attribute Archived control has only Yes/No and no “Leave unchanged” default, yet any stored value is always mapped to isArchived. A stale false from an earlier edit can be sent on unrelated updates and unarchive an attribute that should stay archived.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4245f3a. Configure here.
| list: params.list, | ||
| entry_id: params.entryId, | ||
| }, | ||
| } |
There was a problem hiding this comment.
Stale record fields hijack comments
High Severity
create_comment chooses the record target whenever both recordObject and recordId are present, before considering list and entryId. The block forwards those record fields whenever they are set, so leftover values from a prior record comment can override a new list-and-entry comment.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4245f3a. Configure here.


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