Skip to content

feat(integration): integrate Linear.app#579

Open
TooonyChen wants to merge 1 commit intoaipotheosis-labs:mainfrom
TooonyChen:feat/linear
Open

feat(integration): integrate Linear.app#579
TooonyChen wants to merge 1 commit intoaipotheosis-labs:mainfrom
TooonyChen:feat/linear

Conversation

@TooonyChen
Copy link
Contributor

@TooonyChen TooonyChen commented Aug 7, 2025

🏷️ Ticket

https://www.notion.so/Linear-Integration-23f8378d6a478008b2bcc26232e81e86?source=copy_link

📝 Description

This PR implements a comprehensive Linear integration for the ACI platform, enabling AI agents to interact with Linear's project management features through GraphQL API.

Available Functions:

  1. LINEAR__CREATE_ISSUE - Create new issues with team assignment and priority
  2. LINEAR__GET_ISSUES - List issues with filtering by team, assignee, or state
  3. LINEAR__UPDATE_ISSUE - Update existing issues (title, description, priority, state)
  4. LINEAR__GET_TEAMS - Get all workspace teams with workflow states and labels
  5. LINEAR__GET_USERS - List workspace users (with active/inactive filtering)
  6. LINEAR__GET_ISSUE - Get detailed issue information including comments and attachments
  7. LINEAR__GET_PROJECTS - List all projects with progress and status information
  8. LINEAR__GET_PROJECT - Get detailed project information with milestones and team members

🎥 Demo (if applicable)

📸 Screenshots (if applicable)

fuzzy test prompts and result screenshots are as follows:
https://www.notion.so/Integration-Linear-app-2478378d6a4780c690add23e65a4b38d?source=copy_link

✅ Checklist

  • I have signed the Contributor License Agreement (CLA) and read the contributing guide (required)
  • I have linked this PR to an issue or a ticket (required)
  • I have updated the documentation related to my change if needed
  • I have updated the tests accordingly (required for a bug fix or a new feature)
  • All checks on CI passed

Summary by cubic

Added a full Linear integration, allowing agents to create, update, and query issues, projects, teams, and users through the Linear GraphQL API.

  • New Features
    • Supports creating, updating, and retrieving issues.
    • Lists and fetches details for projects, teams, and users.
    • OAuth2 authentication and connector configuration included.

Summary by CodeRabbit

  • New Features
    • Introduced integration with the Linear project management platform, enabling users to create, update, and retrieve issues, projects, teams, and users directly from the app.
    • Added support for OAuth2 authentication for secure access to Linear.
    • Provided a comprehensive set of functions for managing Linear resources, with strict input validation and detailed parameter options.

@vercel
Copy link

vercel bot commented Aug 7, 2025

Someone is attempting to deploy a commit to the Proxy Team on Vercel.

A member of the Team first needs to authorize it.

@TooonyChen TooonyChen had a problem deploying to CICD_FOR_FORKED_REPO August 7, 2025 05:17 — with GitHub Actions Failure
@TooonyChen TooonyChen had a problem deploying to CICD_FOR_FORKED_REPO August 7, 2025 05:17 — with GitHub Actions Failure
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 7, 2025

Walkthrough

A new Linear app connector module has been introduced, providing a Python class to interact with the Linear issue tracking system via its GraphQL API. Supporting configuration and function specification files have also been added, detailing OAuth2 setup and the connector protocol for exposed functions related to issues, projects, teams, and users.

Changes

Cohort / File(s) Change Summary
Linear App Connector Implementation
backend/aci/server/app_connectors/linear.py
Introduces the Linear class, implementing methods for interacting with Linear's GraphQL API. Supports creating, retrieving, and updating issues, as well as fetching teams, users, projects, and project details. Handles OAuth2 authentication, error handling, and structured API response parsing.
Linear App Metadata and OAuth2 Configuration
backend/apps/linear/app.json
Adds a JSON file describing the Linear app integration, including metadata, logo, provider info, OAuth2 security scheme details (endpoints, scopes), and categorization. No default credentials are provided.
Linear Connector Protocol Functions
backend/apps/linear/functions.json
Adds a JSON file declaring the public connector protocol functions for Linear integration. Specifies 10 functions with parameter schemas for issue, project, team, and user operations. Enforces strict input validation and provides detailed metadata for each function. No implementation logic is present.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant LinearConnector as Linear (AppConnector)
    participant LinearAPI as Linear GraphQL API

    Client->>LinearConnector: create_issue(params)
    LinearConnector->>LinearConnector: _before_execute()
    LinearConnector->>LinearConnector: _make_graphql_request(query, variables)
    LinearConnector->>LinearAPI: POST /graphql (with OAuth2 token)
    LinearAPI-->>LinearConnector: Response (issue data)
    LinearConnector-->>Client: Structured issue data
Loading
sequenceDiagram
    participant Client
    participant LinearConnector as Linear (AppConnector)
    participant LinearAPI as Linear GraphQL API

    Client->>LinearConnector: get_issues(filters)
    LinearConnector->>LinearConnector: _before_execute()
    LinearConnector->>LinearConnector: _make_graphql_request(query, variables)
    LinearConnector->>LinearAPI: POST /graphql (with OAuth2 token)
    LinearAPI-->>LinearConnector: Response (issues list)
    LinearConnector-->>Client: Structured issues data
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

In burrows deep, I code with glee,
A Linear bridge for all to see!
With GraphQL hops and OAuth2 locks,
Issues, teams, and projects in flocks.
JSON blueprints, functions in tow—
Now rabbits track bugs as they grow!
🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@recurseml
Copy link

recurseml bot commented Aug 7, 2025

✨ No issues found! Your code is sparkling clean! ✨

Need help? Join our Discord for support!
https://discord.gg/qEjHQk64Z9

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
backend/aci/server/app_connectors/linear.py (2)

528-588: Use parameterized queries for consistency and safety.

Although include_archived (boolean) and limit (integer) are type-controlled, embedding them directly in the query string is not ideal.

Use GraphQL variables:

-        query = f"""
-        query Projects {{
-          projects(includeArchived: {str(include_archived).lower()}, first: {limit}) {{
+        query = """
+        query Projects($includeArchived: Boolean!, $limit: Int!) {
+          projects(includeArchived: $includeArchived, first: $limit) {

And pass as variables:

variables = {"includeArchived": include_archived, "limit": limit}
result = self._make_graphql_request(query, variables)

1-709: Consider adding missing functions mentioned in PR objectives.

The PR objectives mention functions for comments and attachments operations, but I don't see dedicated methods for these. While get_issue returns comments and attachments, there might be value in having dedicated methods for:

  • Adding comments to issues
  • Managing attachments

Would you like me to help implement these additional methods or open an issue to track this enhancement?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 610467f and 77f2a6d.

📒 Files selected for processing (3)
  • backend/aci/server/app_connectors/linear.py (1 hunks)
  • backend/apps/linear/app.json (1 hunks)
  • backend/apps/linear/functions.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
backend/apps/*/{app.json,functions.json}

📄 CodeRabbit Inference Engine (CLAUDE.md)

Each integration in backend/apps/{app_name}/ must have both app.json and functions.json files

Files:

  • backend/apps/linear/app.json
  • backend/apps/linear/functions.json
backend/apps/*/app.json

📄 CodeRabbit Inference Engine (CLAUDE.md)

Integration app.json files must define metadata, authentication schemes, categories, and visibility

Files:

  • backend/apps/linear/app.json
backend/apps/**/*.json

📄 CodeRabbit Inference Engine (CLAUDE.md)

Backend integration definitions should be stored as JSON configs in backend/apps/

Files:

  • backend/apps/linear/app.json
  • backend/apps/linear/functions.json
backend/apps/*/functions.json

📄 CodeRabbit Inference Engine (CLAUDE.md)

Integration functions.json files must define API endpoints and their schemas

Files:

  • backend/apps/linear/functions.json
backend/**/*.py

📄 CodeRabbit Inference Engine (CLAUDE.md)

backend/**/*.py: Backend Python code must be compatible with Python 3.12+
Backend must use ruff for formatting and linting
Backend must use mypy for type checking

Files:

  • backend/aci/server/app_connectors/linear.py
🧠 Learnings (4)
📚 Learning: applies to backend/apps/*/app.json : integration app.json files must define metadata, authentication...
Learnt from: CR
PR: aipotheosis-labs/aci#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-05T09:39:27.629Z
Learning: Applies to backend/apps/*/app.json : Integration app.json files must define metadata, authentication schemes, categories, and visibility

Applied to files:

  • backend/apps/linear/app.json
📚 Learning: applies to backend/apps/**/*.json : backend integration definitions should be stored as json configs...
Learnt from: CR
PR: aipotheosis-labs/aci#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-05T09:39:27.629Z
Learning: Applies to backend/apps/**/*.json : Backend integration definitions should be stored as JSON configs in backend/apps/

Applied to files:

  • backend/apps/linear/app.json
📚 Learning: applies to backend/apps/*/{app.json,functions.json} : each integration in backend/apps/{app_name}/ m...
Learnt from: CR
PR: aipotheosis-labs/aci#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-05T09:39:27.629Z
Learning: Applies to backend/apps/*/{app.json,functions.json} : Each integration in backend/apps/{app_name}/ must have both app.json and functions.json files

Applied to files:

  • backend/apps/linear/app.json
  • backend/apps/linear/functions.json
📚 Learning: applies to backend/apps/*/functions.json : integration functions.json files must define api endpoint...
Learnt from: CR
PR: aipotheosis-labs/aci#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-05T09:39:27.629Z
Learning: Applies to backend/apps/*/functions.json : Integration functions.json files must define API endpoints and their schemas

Applied to files:

  • backend/apps/linear/app.json
  • backend/apps/linear/functions.json
🔇 Additional comments (2)
backend/apps/linear/app.json (1)

1-26: LGTM! Configuration follows required structure.

The app.json file properly defines all required elements according to the coding guidelines: metadata, authentication schemes, categories, and visibility settings.

backend/apps/linear/functions.json (1)

1-236: LGTM! Functions properly defined with complete schemas.

All function definitions include the required elements as per coding guidelines: API endpoints with proper JSON schemas, parameter definitions, and visibility settings.

Comment on lines +176 to +188
filter_conditions = []

if team_id:
filter_conditions.append(f'team: {{ id: {{ eq: "{team_id}" }} }}')
if assignee_id:
filter_conditions.append(f'assignee: {{ id: {{ eq: "{assignee_id}" }} }}')
if state:
filter_conditions.append(f'state: {{ name: {{ eq: "{state}" }} }}')

filter_str = ""
if filter_conditions:
filter_str = f'filter: {{ {", ".join(filter_conditions)} }}'

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Potential GraphQL injection vulnerability.

Building GraphQL queries using string concatenation with user input can lead to injection attacks. The team_id, assignee_id, and state parameters are directly embedded into the query string without proper escaping.

Use parameterized queries with variables instead:

-    def get_issues(
-        self,
-        team_id: Optional[str] = None,
-        assignee_id: Optional[str] = None,
-        state: Optional[str] = None,
-        limit: int = 50,
-    ) -> Dict[str, Any]:
-        """
-        Get issues from Linear with optional filtering.
-        
-        Function name: LINEAR__GET_ISSUES
-        """
-        # Build filter object
-        filter_conditions = []
-        
-        if team_id:
-            filter_conditions.append(f'team: {{ id: {{ eq: "{team_id}" }} }}')
-        if assignee_id:
-            filter_conditions.append(f'assignee: {{ id: {{ eq: "{assignee_id}" }} }}')
-        if state:
-            filter_conditions.append(f'state: {{ name: {{ eq: "{state}" }} }}')
-        
-        filter_str = ""
-        if filter_conditions:
-            filter_str = f'filter: {{ {", ".join(filter_conditions)} }}'
-
-        query = f"""
-        query Issues {{
-          issues({filter_str}, first: {limit}) {{
+    def get_issues(
+        self,
+        team_id: Optional[str] = None,
+        assignee_id: Optional[str] = None,
+        state: Optional[str] = None,
+        limit: int = 50,
+    ) -> Dict[str, Any]:
+        """
+        Get issues from Linear with optional filtering.
+        
+        Function name: LINEAR__GET_ISSUES
+        """
+        query = """
+        query Issues($filter: IssueFilter, $limit: Int!) {
+          issues(filter: $filter, first: $limit) {

Then build the filter object as a dictionary and pass it as a variable:

filter_obj = {}
if team_id:
    filter_obj["team"] = {"id": {"eq": team_id}}
if assignee_id:
    filter_obj["assignee"] = {"id": {"eq": assignee_id}}
if state:
    filter_obj["state"] = {"name": {"eq": state}}

variables = {"filter": filter_obj if filter_obj else None, "limit": limit}
🤖 Prompt for AI Agents
In backend/aci/server/app_connectors/linear.py around lines 176 to 188, the
current code builds GraphQL filter queries by directly concatenating user inputs
into strings, which risks injection attacks. To fix this, replace the string
concatenation with constructing a filter dictionary using the input parameters
as values, then pass this dictionary as a variable to the GraphQL query. This
involves creating a filter_obj dictionary with keys for team, assignee, and
state only if their values exist, and then passing this filter_obj inside a
variables dictionary to the query execution instead of embedding inputs directly
in the query string.

Comment on lines +394 to +411
filter_str = 'filter: { active: { eq: true } }' if active_only else ''

query = f"""
query Users {{
users({filter_str}) {{
nodes {{
id
name
email
displayName
avatarUrl
active
admin
guest
}}
}}
}}
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve query construction to avoid potential issues.

While the active_only boolean parameter is less risky, constructing GraphQL queries with string concatenation is not a best practice.

Use parameterized queries:

-    def get_users(self, active_only: bool = True) -> Dict[str, Any]:
-        """
-        Get users in the Linear workspace.
-        
-        Function name: LINEAR__GET_USERS
-        """
-        filter_str = 'filter: { active: { eq: true } }' if active_only else ''
-        
-        query = f"""
-        query Users {{
-          users({filter_str}) {{
+    def get_users(self, active_only: bool = True) -> Dict[str, Any]:
+        """
+        Get users in the Linear workspace.
+        
+        Function name: LINEAR__GET_USERS
+        """
+        query = """
+        query Users($filter: UserFilter) {
+          users(filter: $filter) {

And use variables:

variables = {"filter": {"active": {"eq": True}} if active_only else None}
result = self._make_graphql_request(query, variables)
🤖 Prompt for AI Agents
In backend/aci/server/app_connectors/linear.py around lines 394 to 411, the
GraphQL query is constructed using string concatenation, which can lead to
potential issues. Refactor the code to use parameterized queries by defining the
query with a variable for the filter and passing the filter as a variable in the
request. Specifically, define a variables dictionary that sets the filter to
{"active": {"eq": True}} if active_only is True, otherwise None, and pass this
variables dictionary along with the query to the _make_graphql_request method.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic analysis

4 issues found across 3 files • Review in cubic

React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.


query = f"""
query Users {{
users({filter_str}) {{
Copy link
Contributor

Choose a reason for hiding this comment

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

Empty parentheses produce invalid GraphQL when requesting all users without a filter

Prompt for AI agents
Address the following comment on backend/aci/server/app_connectors/linear.py at line 398:

<comment>Empty parentheses produce invalid GraphQL when requesting all users without a filter</comment>

<file context>
@@ -0,0 +1,709 @@
+import json
+from typing import Any, Dict, List, Optional, override
+
+import requests
+
+from aci.common.db.sql_models import LinkedAccount
+from aci.common.exceptions import ACIException
+from aci.common.logging_setup import get_logger
+from aci.common.schemas.security_scheme import (
</file context>


query = f"""
query Issues {{
issues({filter_str}, first: {limit}) {{
Copy link
Contributor

Choose a reason for hiding this comment

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

Leading comma in GraphQL issues call produces invalid query when no filters are supplied

Prompt for AI agents
Address the following comment on backend/aci/server/app_connectors/linear.py at line 191:

<comment>Leading comma in GraphQL issues call produces invalid query when no filters are supplied</comment>

<file context>
@@ -0,0 +1,709 @@
+import json
+from typing import Any, Dict, List, Optional, override
+
+import requests
+
+from aci.common.db.sql_models import LinkedAccount
+from aci.common.exceptions import ACIException
+from aci.common.logging_setup import get_logger
+from aci.common.schemas.security_scheme import (
</file context>

@@ -0,0 +1,709 @@
import json
from typing import Any, Dict, List, Optional, override
Copy link
Contributor

Choose a reason for hiding this comment

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

Importing override from typing will break on Python versions earlier than 3.12, causing a runtime ImportError and making the connector unusable

Prompt for AI agents
Address the following comment on backend/aci/server/app_connectors/linear.py at line 2:

<comment>Importing override from typing will break on Python versions earlier than 3.12, causing a runtime ImportError and making the connector unusable</comment>

<file context>
@@ -0,0 +1,709 @@
+import json
+from typing import Any, Dict, List, Optional, override
+
+import requests
</file context>

filter_conditions = []

if team_id:
filter_conditions.append(f'team: {{ id: {{ eq: "{team_id}" }} }}')
Copy link
Contributor

Choose a reason for hiding this comment

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

Directly embedding user-supplied values into the GraphQL query string opens the door to injection vulnerabilities. Construct a variables object and pass it separately to the query instead of interpolating IDs into the string.

Prompt for AI agents
Address the following comment on backend/aci/server/app_connectors/linear.py at line 179:

<comment>Directly embedding user-supplied values into the GraphQL query string opens the door to injection vulnerabilities. Construct a variables object and pass it separately to the query instead of interpolating IDs into the string.</comment>

<file context>
@@ -0,0 +1,709 @@
+import json
+from typing import Any, Dict, List, Optional, override
+
+import requests
+
+from aci.common.db.sql_models import LinkedAccount
+from aci.common.exceptions import ACIException
+from aci.common.logging_setup import get_logger
+from aci.common.schemas.security_scheme import (
</file context>

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

Comments