Skip to content

feat(attio): add attribute tools + fix API alignment gaps#5445

Open
waleedlatif1 wants to merge 4 commits into
stagingfrom
attio-integration-audit
Open

feat(attio): add attribute tools + fix API alignment gaps#5445
waleedlatif1 wants to merge 4 commits into
stagingfrom
attio-integration-audit

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Add 4 new tools for Attio's attribute schema management: attio_list_attributes, get_attribute, create_attribute, update_attribute
  • Fix missing completed_at output on task tools
  • Fix note tags output to match Attio's real response shape (workspace-member vs record tag variants)
  • Fix attribute outputs missing is_default_value_enabled, default_value, relationship fields
  • Fix create_comment sending both entry and thread_id in the same request (Attio requires exactly one)
  • Wire attribute pagination and task sort into the block UI

Type of Change

  • New feature
  • Bug fix

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

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

- 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
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 6, 2026 10:55pm

Request Review

@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Create/update attribute and comment paths can mutate CRM schema and data; invalid JSON now fails fast on record writes (behavior change). No auth changes; mostly additive tools plus targeted bug fixes.

Overview
Adds four attribute schema tools (list, get, create, update) on Attio objects/lists, registers them in the tool registry, and exposes full block UI (target, identifier, type/config, pagination, “leave unchanged” on updates).

API alignment fixes: create_comment now sends exactly one of thread_id, record, or entry (optional list/entry vs new record fields); task tools expose completedAt and list_tasks supports completed_at sort; note tags are normalized via mapNoteTags for workspace-member vs record variants; attribute outputs include default-value and relationship fields.

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.

Comment thread apps/sim/blocks/blocks/attio.ts
Comment thread apps/sim/tools/attio/update_attribute.ts
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds four new Attio attribute-management tools (list_attributes, get_attribute, create_attribute, update_attribute) and fixes several API-alignment gaps: completed_at on task responses, correct note tags shape, the create_comment mutual-exclusivity bug, and improved JSON parse error handling across record/filter params.

  • New attribute tools — each tool correctly maps Attio's response fields including the previously-missing is_default_value_enabled, default_value, and relationship fields; all four are wired into the block UI with appropriate dropdown options.
  • Comment fixcreate_comment now sends exactly one of thread_id, record, or entry in the request body and throws a clear error when no valid combination is supplied.
  • Boolean-update patterntaskIsCompletedUpdate and listWorkspaceAccessUpdate correctly adopt the "Leave unchanged" sentinel pattern for update operations, but attributeIsArchived in the block omits both the sentinel option and its default value, which could cause every update_attribute call to unconditionally send is_archived: false.

Confidence Score: 4/5

Safe to merge with one targeted fix: the attributeIsArchived dropdown needs a "Leave unchanged" sentinel to avoid silently overwriting the archived state on every update_attribute call.

The attributeIsArchived dropdown in the block UI deviates from the established "Leave unchanged" pattern used by every other optional boolean update field (taskIsCompletedUpdate, listWorkspaceAccessUpdate). Without a sentinel option and a value: () => 'unchanged' default, the field may initialise to 'false', causing every update_attribute operation to include is_archived: false — silently unarchiving attributes the user had no intention of touching. All other changes are additive and well-implemented.

apps/sim/blocks/blocks/attio.ts — the attributeIsArchived dropdown configuration (lines 227–236).

Important Files Changed

Filename Overview
apps/sim/blocks/blocks/attio.ts Adds 4 new attribute operations and several UI fixes (task/list update dropdowns, comment entry/record routing). The attributeIsArchived dropdown is missing the "Leave unchanged" sentinel option and default value that all parallel boolean-update dropdowns use, risking unintended is_archived=false writes on every update_attribute call.
apps/sim/tools/attio/create_attribute.ts New tool for creating an Attio attribute. Properly throws on JSON parse failure, always includes config: {} as the API requires even without user input, and maps all response fields correctly.
apps/sim/tools/attio/update_attribute.ts New tool for updating an Attio attribute. Now throws on config JSON parse failure (fixing the silent-overwrite issue). Only sends fields that are explicitly provided.
apps/sim/tools/attio/list_attributes.ts New tool for listing attributes on an object or list. Pagination and show_archived are correctly wired into query params; response mapping is thorough.
apps/sim/tools/attio/get_attribute.ts New tool for fetching a single attribute by slug or ID. Clean and consistent with the other attribute tools.
apps/sim/tools/attio/create_comment.ts Fixed mutual-exclusivity bug: body now correctly sends exactly one of thread_id, record, or entry, and throws a clear error when none of the required combinations is provided.
apps/sim/tools/attio/types.ts Adds ATTRIBUTE_OUTPUT_PROPERTIES, NoteTagOutput/mapNoteTags, and completedAt to all task response shapes. WorkspaceMemberAccess typed as unknown (accurate per API). Good coverage of new types.
apps/sim/tools/attio/list_tasks.ts Adds completedAt to task output and updates sort description to include completed_at variants.

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)"]
Loading
%%{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)"]
Loading

Reviews (6): Last reviewed commit: "fix(attio): gate operation-specific para..." | Re-trigger Greptile

Comment thread apps/sim/tools/attio/create_attribute.ts Outdated
- 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)
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Addressed round-1 feedback in a87e0fa:

  • update_attribute/create_attribute: invalid config JSON now throws instead of silently overwriting with {}
  • create_attribute: config omitted entirely when not provided (was always sending {})
  • Required/Unique dropdowns for create/update attribute now default to "Leave unchanged" instead of "No", so update_attribute no longer clears existing required/unique constraints on unrelated edits (Cursor's High severity finding)
  • list_tasks sort param description now documents the completed_at:asc/desc variants (Greptile's outside-diff P2)

@greptile review
@cursor review

…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)
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

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

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/attio.ts
Comment thread apps/sim/blocks/blocks/attio.ts
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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

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.

@greptile review
@cursor review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ 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',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4245f3a. Configure here.

list: params.list,
entry_id: params.entryId,
},
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4245f3a. Configure 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