Skip to content

Conversation

omby8888
Copy link
Contributor

@omby8888 omby8888 commented Oct 16, 2025

User description

Description

GitHub ocean execution agent support

Type of change

Please leave one option from the following and delete the rest:

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • New Integration (non-breaking change which adds a new integration)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Non-breaking change (fix of existing functionality that will not change current behavior)
  • Documentation (added/updated documentation)

All tests should be run against the port production environment(using a testing org).

Core testing checklist

  • Integration able to create all default resources from scratch
  • Resync finishes successfully
  • Resync able to create entities
  • Resync able to update entities
  • Resync able to detect and delete entities
  • Scheduled resync able to abort existing resync and start a new one
  • Tested with at least 2 integrations from scratch
  • Tested with Kafka and Polling event listeners
  • Tested deletion of entities that don't pass the selector

Integration testing checklist

  • Integration able to create all default resources from scratch
  • Completed a full resync from a freshly installed integration and it completed successfully
  • Resync able to create entities
  • Resync able to update entities
  • Resync able to detect and delete entities
  • Resync finishes successfully
  • If new resource kind is added or updated in the integration, add example raw data, mapping and expected result to the examples folder in the integration directory.
  • If resource kind is updated, run the integration with the example data and check if the expected result is achieved
  • If new resource kind is added or updated, validate that live-events for that resource are working as expected
  • Docs PR link here

Preflight checklist

  • Handled rate limiting
  • Handled pagination
  • Implemented the code in async
  • Support Multi account

Screenshots

Include screenshots from your environment showing how the resources of the integration will look.

API Documentation

Provide links to the API documentation used for this integration.


PR Type

Enhancement


Description

  • Added GitHub Actions workflow dispatch executor for Port actions

  • Implemented webhook processing for workflow run status tracking

  • Enhanced rate limiter with improved status reporting and caching

  • Added authentication context management for GitHub users


Diagram Walkthrough

flowchart LR
  A["Port Action Trigger"] --> B["DispatchWorkflowExecutor"]
  B --> C["GitHub API: Dispatch Workflow"]
  C --> D["Poll for Workflow Run ID"]
  D --> E["Update Port Run with External ID"]
  F["GitHub Webhook: workflow_run"] --> G["DispatchWorkflowWebhookProcessor"]
  G --> H["Update Port Run Status"]
Loading

File Walkthrough

Relevant files
Enhancement
13 files
dispatch_workflow_executor.py
Implement workflow dispatch executor with status tracking
+270/-0 
abstract_github_executor.py
Add base executor class with rate limit checks                     
+24/-0   
utils.py
Add utility for building workflow run external IDs             
+5/-0     
base_workflow_run_webhook_processor.py
Create base processor for workflow run webhooks                   
+36/-0   
workflow_run_webhook_processor.py
Refactor to extend base workflow run processor                     
+4/-22   
auth.py
Add authenticated user context management with caching     
+31/-0   
base_client.py
Add ignore_default_errors parameter and improve rate limit access
+13/-6   
limiter.py
Refactor rate limit info to public property                           
+11/-30 
utils.py
Add utilization percentage property to RateLimitInfo         
+4/-0     
registry.py
Extract webhook path constant and simplify registration   
+24/-23 
webhook_client.py
Use centralized webhook path constant                                       
+2/-1     
main.py
Register action executors and update webhook creation logic
+9/-3     
graphql_client.py
Add ignore_default_errors parameter to method signature   
+1/-0     
Configuration changes
3 files
registry.py
Register action executors with Ocean framework                     
+9/-0     
spec.yaml
Add execution agent configuration with workflow dispatch action
+21/-0   
launch.json
Add debug configuration for GitHub integration                     
+11/-0   
Bug fix
1 files
integration.py
Update webhook manager reference in execution manager       
+3/-1     
Documentation
1 files
CHANGELOG.md
Document workflow dispatch support in changelog                   
+6/-0     
Dependencies
1 files
pyproject.toml
Bump version to 1.6.0-beta                                                             
+1/-1     
Tests
1 files
test_rate_limiter.py
Update tests for refactored rate limiter interface             
+10/-7   

Copy link
Contributor

qodo-merge-pro bot commented Oct 16, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Stale auth cache

Description: Global cached authentication context (_auth_context) is never refreshed or invalidated,
which can lead to stale user identity being used for authorization and event filtering
across long-lived processes or credential rotations.
auth.py [18-31]

Referred Code
_auth_context: Optional[AuthContext] = None
_auth_lock = asyncio.Lock()


async def get_authenticated_user() -> AuthContext:
    global _auth_context
    if _auth_context is not None:
        return _auth_context

    async with _auth_lock:
        client = create_github_client()
        response = await client.send_api_request(f"{client.base_url}/user")
        _auth_context = AuthContext.parse_obj(response)
        return _auth_context
Information disclosure

Description: Error handling leaks raw GitHub error messages back to callers, potentially exposing
internal details; additionally, polling logic relies on ISO timestamp filtering and actor
match which may misattribute runs if clocks drift, enabling wrong run association and
status updates.
dispatch_workflow_executor.py [221-270]

Referred Code
try:
    isoDate = datetime.now(timezone.utc).isoformat()
    await self.rest_client.make_request(
        f"{self.rest_client.base_url}/repos/{self.rest_client.organization}/{repo}/actions/workflows/{workflow}/dispatches",
        method="POST",
        json_data={
            "ref": ref,
            "inputs": inputs,
        },
        ignore_default_errors=False,
    )

    # Get the workflow run id
    workflow_runs = []
    attempts_made = 0
    while (
        len(workflow_runs) == 0 and attempts_made < MAX_WORKFLOW_POLL_ATTEMPTS
    ):
        authenticated_user = await get_authenticated_user()
        response = await self.rest_client.send_api_request(
            f"{self.rest_client.base_url}/repos/{self.rest_client.organization}/{repo}/actions/runs",


 ... (clipped 29 lines)
Masked HTTP errors

Description: On ignored HTTP errors, the client returns a fabricated 200 response with empty JSON,
which can mask real failures and lead to insecure logic paths assuming success without
validation.
base_client.py [118-126]

Referred Code
if not self.rate_limiter.is_rate_limit_response(response):
    if self._should_ignore_error(
        e, resource, ignored_errors, ignore_default_errors
    ):
        return Response(200, content=b"{}")

logger.error(
    f"GitHub API error for endpoint '{resource}': Status {response.status_code}, "
    f"Method: {method}, Response: {response.text}"
Ticket Compliance
🎫 No ticket provided
- [ ] Create ticket/issue <!-- /create_ticket --create_ticket=true -->

</details></td></tr>
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consider a more robust workflow tracking method

Improve workflow tracking by passing the Port run.id as an input to the GitHub
workflow. This allows the workflow to report back to Port, creating a reliable
link and removing the need for fragile polling logic.

Examples:

integrations/github/github/actions/dispatch_workflow_executor.py [221-265]
        try:
            isoDate = datetime.now(timezone.utc).isoformat()
            await self.rest_client.make_request(
                f"{self.rest_client.base_url}/repos/{self.rest_client.organization}/{repo}/actions/workflows/{workflow}/dispatches",
                method="POST",
                json_data={
                    "ref": ref,
                    "inputs": inputs,
                },
                ignore_default_errors=False,

 ... (clipped 35 lines)

Solution Walkthrough:

Before:

async def execute(self, run: ActionRun):
    # ...
    dispatch_time = datetime.now()
    await self.rest_client.make_request(
        # ... dispatch workflow ...
    )

    # Poll to find the workflow run that was just created
    workflow_runs = []
    attempts = 0
    while not workflow_runs and attempts < MAX_ATTEMPTS:
        response = await self.rest_client.send_api_request(
            # ... search for runs created after dispatch_time by the current user ...
        )
        workflow_runs = response.get("workflow_runs", [])
        await asyncio.sleep(DELAY)
    
    # Assume the first result is the correct one
    external_id = build_external_id(workflow_runs[0])
    await ocean.port_client.patch_run(run.id, {"external_run_id": external_id})

After:

async def execute(self, run: ActionRun):
    # ...
    # Pass Port's run.id directly to the workflow
    inputs = run.payload.integrationActionExecutionProperties.get("workflowInputs", {})
    inputs["port_run_id"] = run.id

    await self.rest_client.make_request(
        # ... dispatch workflow ...
        json_data={
            "ref": ref,
            "inputs": inputs,
        },
    )
    # No polling needed. The workflow is now responsible for
    # reporting back to Port using the provided `port_run_id`
    # to establish the link with its GitHub run ID.
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a significant design weakness in the polling mechanism for tracking workflow runs, which is prone to race conditions, and proposes a much more robust and reliable alternative that improves the core functionality of the PR.

High
Possible issue
Align default value with the specification

Change the default value for reportWorkflowStatus from False to True in the
get() method to match the spec.yaml definition.

integrations/github/github/actions/dispatch_workflow_executor.py [162-168]

 if (
     action_run
     and action_run.status == RunStatus.IN_PROGRESS
     and action_run.payload.integrationActionExecutionProperties.get(
-        "reportWorkflowStatus", False
+        "reportWorkflowStatus", True
     )
 ):
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a discrepancy between the spec.yaml default for reportWorkflowStatus and its implementation, fixing a bug where the status would not be reported by default.

Medium
Add robust error handling for non-JSON responses

Improve error handling by wrapping a json.loads() call in a try-except block to
prevent crashes on non-JSON error responses.

integrations/github/github/actions/dispatch_workflow_executor.py [266-270]

 except Exception as e:
     error_message = str(e)
     if isinstance(e, httpx.HTTPStatusError):
-        error_message = json.loads(e.response.text).get("message", str(e))
+        try:
+            error_message = json.loads(e.response.text).get("message", str(e))
+        except json.JSONDecodeError:
+            error_message = e.response.text
     raise Exception(f"Error dispatching workflow: {error_message}")
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential json.JSONDecodeError in the exception handling logic and proposes a robust fix, improving the reliability of error reporting.

Medium
Fix race condition in authentication caching

Fix a race condition in get_authenticated_user by adding a second check for
_auth_context inside the lock to prevent redundant API calls.

integrations/github/github/context/auth.py [18-31]

 _auth_context: Optional[AuthContext] = None
 _auth_lock = asyncio.Lock()
 
 
 async def get_authenticated_user() -> AuthContext:
     global _auth_context
     if _auth_context is not None:
         return _auth_context
 
     async with _auth_lock:
+        if _auth_context is not None:
+            return _auth_context
         client = create_github_client()
         response = await client.send_api_request(f"{client.base_url}/user")
         _auth_context = AuthContext.parse_obj(response)
         return _auth_context
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a race condition in the caching logic and proposes a valid fix using the double-checked locking pattern, which prevents redundant API calls.

Medium
  • More

@omby8888 omby8888 changed the title GitHub ocean execution agent support [Integrations][Github] GitHub ocean execution agent support Oct 16, 2025
Copy link
Contributor

Code Coverage Artifact 📈: https://github.com/port-labs/ocean/actions/runs/18553679863/artifacts/4285546016

Code Coverage Total Percentage: 87.65%

@IdansPort
Copy link
Contributor

seems like the ci/cd is falling

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants