Skip to content

BED-9185: add enterprise GitHub runner support - #29

Open
jaredcatkinson wants to merge 6 commits into
mainfrom
feature/BED-9185-enterprise-github-runner
Open

BED-9185: add enterprise GitHub runner support#29
jaredcatkinson wants to merge 6 commits into
mainfrom
feature/BED-9185-enterprise-github-runner

Conversation

@jaredcatkinson

@jaredcatkinson jaredcatkinson commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add collection and graph modeling for GH_EnterpriseRunnerGroup and GH_EnterpriseRunner
  • distinguish organization runner groups with GH_OrgRunnerGroup while preserving the generic GH_RunnerGroup and GH_Runner labels across scopes
  • model enterprise runner group delegation and inheritance through GH_AssignedTo, GH_InheritedFrom, GH_GrantsAccessTo, and composed GH_CanUseRunner edges
  • normalize runner and runner group object IDs and display names across repository, organization, and enterprise scopes
  • document the new runner nodes and relationships and update the extension schema metadata

Validation

  • ran uv run pytest tests/test_runner_models.py tests/test_enterprise_resources.py tests/test_runner_resources.py
  • reran collection and verified enterprise runner groups, enterprise runners, inherited organization runner groups, and composed repository-to-enterprise-runner edges in the converted graph output

Scope

  • this first pass covers self-hosted runners only
  • workflow restrictions and job-log or machine-name correlation are intentionally deferred

Summary by CodeRabbit

  • New Features
    • Added support for enterprise- and organization-scoped GitHub Actions runners and runner groups.
    • Added runner assignments, inherited group relationships, repository access, memberships, and organization visibility.
    • Added generic secrets and variables, GitHub Enterprise configuration, and schema version 1.3.1.
  • Documentation
    • Expanded guidance for runners, runner groups, assignments, inheritance, and access relationships.
  • Tests
    • Added coverage for enterprise resources, runner access, inheritance, lookups, pagination, and collection.

Collect enterprise runner groups, runners, memberships, and organization assignments through the PAT-backed enterprise API.

Model enterprise and organization runner groups separately while retaining generic runner labels, compose repository access through group relationships, and normalize runner and runner-group identifiers and names across scopes.

Add lookup, preprocessing, schema, and focused test coverage for inherited runner groups and enterprise runner resources.
Add node and edge descriptions for enterprise, organization, and repository runner types along with inherited runner-group and access relationships.

Update the GH_Contains description to cover the expanded runner containment model.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f59344e-3de0-464e-8432-eaff8ab8e6a5

📥 Commits

Reviewing files that changed from the base of the PR and between 279853f and 7ecc7c3.

📒 Files selected for processing (2)
  • src/openhound_github/resources/organization.py
  • tests/test_runner_resources.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_runner_resources.py
  • src/openhound_github/resources/organization.py

Walkthrough

The change adds enterprise and organization runner resources, scoped runner graph models, inheritance and repository-access relationships, lookup utilities, schema updates, and tests for collection and graph resolution.

Changes

Runner modeling and access flow

Layer / File(s) Summary
Runner contracts and schema
src/openhound_github/kinds/*, src/openhound_github/runner_ids.py, src/openhound_github/transforms.py, extension/schema.json, descriptions/...
The schema defines scoped runner nodes, inheritance and access edges, generic secret and variable kinds, and a GitHub Enterprise environment. Shared helpers, input tables, and model exports support the new assets.
Enterprise runner collection
src/openhound_github/resources/enterprise.py, src/openhound_github/main.py, tests/test_enterprise_resources.py
Enterprise transformers collect runner groups, runners, memberships, and selected-visibility organization assignments through the SSO client. Tests cover pagination and emitted metadata.
Organization runner-group access
src/openhound_github/resources/organization.py, tests/test_runner_resources.py
Organization runner groups now produce access records. Native groups produce membership rows. Inherited groups are skipped.
Scoped graph modeling and resolution
src/openhound_github/models/runner.py, src/openhound_github/lookup.py, tests/test_runner_models.py
Runner assets distinguish organization, enterprise, and repository scope. The graph models inheritance, containment, repository grants, memberships, and composed runner-use edges. Cached lookups resolve inherited groups and runners, including missing and ambiguous cases.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SSOClient
  participant EnterpriseResources
  participant InputTables
  participant RunnerModels
  SSOClient->>EnterpriseResources: collect enterprise runner data
  EnterpriseResources->>InputTables: emit typed runner records
  InputTables->>RunnerModels: provide scoped runner data
  RunnerModels->>RunnerModels: resolve inheritance and repository access
Loading

Poem

A rabbit maps runners with care,
Enterprise groups join the affair.
Organization paths inherit,
Repository links now appear.
Tests confirm each hop is clear.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding enterprise GitHub Actions runner support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/BED-9185-enterprise-github-runner

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/openhound_github/transforms.py (1)

55-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the ensure_optional_input_tables docstring.

The function now creates enterprise runner tables, but the docstring still describes only zero-row branch-policy inputs. Update the documented contract to include enterprise runner resources.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhound_github/transforms.py` around lines 55 - 74, Update the
docstring for ensure_optional_input_tables to document that it creates the
enterprise organization, runner group, runner group organization, and runner
group membership input tables in addition to the existing zero-row branch-policy
tables.
tests/test_runner_models.py (2)

251-254: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the row-order assertion order-independent.

The query in enterprise_organization_node_ids has no ORDER BY. SQL does not guarantee row order without one. The assertion compares an ordered list, so the test depends on DuckDB scan order. Compare sets instead.

💚 Proposed fix
-    assert GithubLookup(connection).enterprise_organization_node_ids("ENT_1") == [
-        ("ORG_1",),
-        ("ORG_2",),
-    ]
+    assert set(GithubLookup(connection).enterprise_organization_node_ids("ENT_1")) == {
+        ("ORG_1",),
+        ("ORG_2",),
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_runner_models.py` around lines 251 - 254, Update the assertion for
GithubLookup.enterprise_organization_node_ids("ENT_1") to compare sets rather
than ordered lists, preserving the expected ("ORG_1",) and ("ORG_2",) rows while
making the test independent of database row order.

257-283: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add coverage for missing enterprise tables.

LookupManager._find_all_objects returns [] for duckdb.CatalogException. Add a test with no enterprise_* tables. Assert that group lookup returns None and runner lookup returns [].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_runner_models.py` around lines 257 - 283, Add a test alongside
test_inherited_org_runner_group_lookup_resolves_all_and_selected_assignments
that uses an empty DuckDB schema without creating any enterprise_* tables.
Verify the group lookup returns None when tables are absent, while the runner
lookup returns an empty list, covering LookupManager._find_all_objects handling
of duckdb.CatalogException.

Source: Linters/SAST tools

src/openhound_github/lookup.py (1)

93-97: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Log ambiguous inherited runner-group matches.

The method returns None when the query matches zero rows or more than one row. The caller in src/openhound_github/models/runner.py then drops the GH_InheritedFrom edge and the composed GH_CanUseRunner edges without any signal. An operator cannot distinguish "no enterprise data collected" from "duplicate group name across enterprises". Add a debug or warning log for the ambiguous case.

🔭 Proposed observability improvement
-        if not rows or len(rows) != 1:
-            return None
+        if not rows:
+            return None
+        if len(rows) != 1:
+            logger.warning(
+                "Ambiguous inherited runner group '%s' for organization '%s': "
+                "%d candidate enterprise groups; skipping inheritance edges.",
+                group_name,
+                org_node_id,
+                len(rows),
+            )
+            return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhound_github/lookup.py` around lines 93 - 97, Update the lookup
method containing the rows validation to log a debug or warning message when
multiple rows are returned, identifying the ambiguous runner-group match before
returning None. Keep the no-row case silent and preserve the existing return
behavior for both zero and duplicate matches.
src/openhound_github/models/runner.py (1)

481-487: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared repository-visibility resolution.

OrgRunnerGroupAccess.repository_node_ids and OrgRunnerGroupMembership._can_use_runner_edges (Lines 602-611) resolve repository node IDs with identical logic: all maps to repository_node_ids_for_org, private maps to private_repository_node_ids_for_org, and any other value maps to accessible_repo_node_ids. The two copies can diverge when GitHub adds a visibility value. Move the logic into one shared helper or a mixin.

♻️ Proposed refactor
+def _repository_node_ids_for_visibility(
+    lookup, visibility: str | None, org_login: str, accessible_repo_node_ids: list[str]
+):
+    if visibility == "all":
+        return lookup.repository_node_ids_for_org(org_login)
+    if visibility == "private":
+        return lookup.private_repository_node_ids_for_org(org_login)
+    return [(repo_node_id,) for repo_node_id in accessible_repo_node_ids]
     `@property`
     def repository_node_ids(self):
-        if self.runner_group_visibility == "all":
-            return self._lookup.repository_node_ids_for_org(self.org_login)
-        if self.runner_group_visibility == "private":
-            return self._lookup.private_repository_node_ids_for_org(self.org_login)
-        return [(repo_node_id,) for repo_node_id in self.accessible_repo_node_ids]
+        return _repository_node_ids_for_visibility(
+            self._lookup,
+            self.runner_group_visibility,
+            self.org_login,
+            self.accessible_repo_node_ids,
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhound_github/models/runner.py` around lines 481 - 487, Extract the
runner-group visibility mapping from OrgRunnerGroupAccess.repository_node_ids
and OrgRunnerGroupMembership._can_use_runner_edges into one shared helper or
mixin. Preserve the existing all, private, and fallback accessible_repo_node_ids
behavior, and update both callers to use the shared implementation so future
visibility values are handled consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhound_github/models/__init__.py`:
- Around line 41-51: Restore the deprecated RunnerGroup compatibility export in
the models package, including the corresponding symbol in __all__ if present, so
existing from openhound_github.models import RunnerGroup imports continue to
work. Keep the new runner-group types unchanged and expose RunnerGroup as an
adapter or alias to the replacement API.

In `@src/openhound_github/resources/organization.py`:
- Around line 1238-1250: Wrap the client.paginate flow in the runner-group
membership transformer with request-failure handling. Log the pagination error
using the existing logging approach, then stop processing that runner group and
yield no membership rows when the endpoint request fails; preserve normal
pagination and row generation on success.

In `@src/openhound_github/runner_ids.py`:
- Around line 1-6: Update runner_group_node_id and runner_node_id to reject a
None scope_node_id before constructing identifiers, either by making the
parameter required or raising an appropriate error. Preserve valid scoped
identifier formatting and prevent any None_runner_* values from being returned.

---

Nitpick comments:
In `@src/openhound_github/lookup.py`:
- Around line 93-97: Update the lookup method containing the rows validation to
log a debug or warning message when multiple rows are returned, identifying the
ambiguous runner-group match before returning None. Keep the no-row case silent
and preserve the existing return behavior for both zero and duplicate matches.

In `@src/openhound_github/models/runner.py`:
- Around line 481-487: Extract the runner-group visibility mapping from
OrgRunnerGroupAccess.repository_node_ids and
OrgRunnerGroupMembership._can_use_runner_edges into one shared helper or mixin.
Preserve the existing all, private, and fallback accessible_repo_node_ids
behavior, and update both callers to use the shared implementation so future
visibility values are handled consistently.

In `@src/openhound_github/transforms.py`:
- Around line 55-74: Update the docstring for ensure_optional_input_tables to
document that it creates the enterprise organization, runner group, runner group
organization, and runner group membership input tables in addition to the
existing zero-row branch-policy tables.

In `@tests/test_runner_models.py`:
- Around line 251-254: Update the assertion for
GithubLookup.enterprise_organization_node_ids("ENT_1") to compare sets rather
than ordered lists, preserving the expected ("ORG_1",) and ("ORG_2",) rows while
making the test independent of database row order.
- Around line 257-283: Add a test alongside
test_inherited_org_runner_group_lookup_resolves_all_and_selected_assignments
that uses an empty DuckDB schema without creating any enterprise_* tables.
Verify the group lookup returns None when tables are absent, while the runner
lookup returns an empty list, covering LookupManager._find_all_objects handling
of duckdb.CatalogException.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 473bfc7f-1a3a-49fc-8f3b-4f6d2193a5f9

📥 Commits

Reviewing files that changed from the base of the PR and between b2fd3f6 and eb506e0.

📒 Files selected for processing (24)
  • descriptions/edges/GH_AssignedTo.md
  • descriptions/edges/GH_CanUseRunner.md
  • descriptions/edges/GH_Contains.md
  • descriptions/edges/GH_GrantsAccessTo.md
  • descriptions/edges/GH_InheritedFrom.md
  • descriptions/nodes/GH_EnterpriseRunner.md
  • descriptions/nodes/GH_EnterpriseRunnerGroup.md
  • descriptions/nodes/GH_OrgRunner.md
  • descriptions/nodes/GH_OrgRunnerGroup.md
  • descriptions/nodes/GH_RepoRunner.md
  • extension/schema.json
  • src/openhound_github/kinds/edges.py
  • src/openhound_github/kinds/nodes.py
  • src/openhound_github/lookup.py
  • src/openhound_github/main.py
  • src/openhound_github/models/__init__.py
  • src/openhound_github/models/runner.py
  • src/openhound_github/resources/enterprise.py
  • src/openhound_github/resources/organization.py
  • src/openhound_github/runner_ids.py
  • src/openhound_github/transforms.py
  • tests/test_enterprise_resources.py
  • tests/test_runner_models.py
  • tests/test_runner_resources.py

Comment thread src/openhound_github/models/__init__.py
Comment thread src/openhound_github/resources/organization.py Outdated
Comment thread src/openhound_github/runner_ids.py

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhound_github/resources/organization.py`:
- Around line 1238-1261: In the runner pagination loop for
org_runner_group_memberships, narrow the broad exception handler around
client.paginate to catch only requests.RequestException. Keep the existing
request-error logging and early return for transport failures, while allowing
KeyError, ValueError, and paginator/data-processing errors to propagate visibly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4ce5949-d047-4637-ba45-cd34b8d59028

📥 Commits

Reviewing files that changed from the base of the PR and between eb506e0 and 99ea854.

📒 Files selected for processing (2)
  • src/openhound_github/resources/organization.py
  • tests/test_runner_resources.py

Comment thread src/openhound_github/resources/organization.py
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