Skip to main content

GitHub SCM Plugin

The GitHub SCM plugin provides comprehensive GitHub integration for pull request management, CI/CD monitoring, code review tracking, and merge readiness checks.

Overview

This plugin uses the GitHub CLI (gh) to interact with the GitHub API. It monitors PR state, CI checks, code reviews, and determines when PRs are ready to merge. The orchestrator uses this information to automatically handle routine tasks and notify humans only when their judgment is needed.
The SCM plugin is designed for push-based workflows: agents create PRs, the orchestrator monitors CI/reviews, and auto-merges when ready.

Configuration

Configure the GitHub SCM plugin in your agent-orchestrator.yaml:
plugins:
  scm:
    name: github

projects:
  - id: my-app
    repo: owner/repo-name  # GitHub repository in owner/repo format

Configuration Options

The GitHub SCM plugin requires no additional configuration parameters. All settings are derived from the project’s repo field.
repo
string
required
GitHub repository in owner/repo format (e.g., octocat/hello-world).

Requirements

Prerequisites:
  • GitHub CLI (gh) installed and in PATH
  • Authenticated with gh auth login
  • Read/write access to pull requests in the target repository
  • Repository must have Actions or Checks configured for CI

Installing GitHub CLI

# macOS
brew install gh

# Linux (Debian/Ubuntu)
sudo apt install gh

# Login
gh auth login

Verifying Authentication

# Check authentication status
gh auth status

# Test PR access
gh pr list --repo owner/repo-name --limit 5

Features

PR Detection and State

Detect PR Find a PR for a specific branch:
const pr = await scm.detectPR(session, project);
// Returns: PRInfo | null
// { number, url, title, owner, repo, branch, baseBranch, isDraft }
Get PR State Check if a PR is open, closed, or merged:
const state = await scm.getPRState(pr);
// Returns: "open" | "closed" | "merged"
Get PR Summary Get high-level PR information:
const summary = await scm.getPRSummary(pr);
// Returns: { state, title, additions, deletions }

PR Lifecycle

Merge PR Merge a PR with configurable merge method:
await scm.mergePR(pr, "squash");  // "squash" | "merge" | "rebase"
// Automatically deletes the branch after merge
Close PR Close a PR without merging:
await scm.closePR(pr);

CI/CD Monitoring

Get CI Checks Fetch all CI checks for a PR:
const checks = await scm.getCIChecks(pr);
// Returns: CICheck[]
// [
//   { name: "Build", status: "passed", url: "...", conclusion: "SUCCESS", ... },
//   { name: "Test", status: "running", ... },
//   { name: "Lint", status: "failed", ... }
// ]
Get CI Summary Get high-level CI status:
const status = await scm.getCISummary(pr);
// Returns: "passing" | "failing" | "pending" | "none"
CI Summary Logic:
  • passing: All checks passed (at least one check exists)
  • failing: At least one check failed
  • pending: No failures, but at least one check is pending/running
  • none: No checks configured or PR is merged/closed

CI Check Status Mapping

GitHub Check State → CI Status:
GitHub StateCI Status
SUCCESSpassed
PENDING, QUEUEDpending
IN_PROGRESSrunning
FAILURE, TIMED_OUT, CANCELLEDfailed
SKIPPED, NEUTRALskipped
Unknownfailed (fail-closed)

Code Review Tracking

Get Reviews Fetch all reviews for a PR:
const reviews = await scm.getReviews(pr);
// Returns: Review[]
// [
//   { author: "reviewer1", state: "approved", body: "LGTM", submittedAt: Date },
//   { author: "reviewer2", state: "changes_requested", body: "...", submittedAt: Date }
// ]
Get Review Decision Get the overall review decision (GitHub’s aggregate status):
const decision = await scm.getReviewDecision(pr);
// Returns: "approved" | "changes_requested" | "pending" | "none"

Review Comments

Get Pending Comments Fetch unresolved review comments (excludes bot comments):
const comments = await scm.getPendingComments(pr);
// Returns: ReviewComment[]
// [
//   {
//     id: "comment-123",
//     author: "reviewer1",
//     body: "This could be refactored...",
//     path: "src/index.ts",
//     line: 42,
//     isResolved: false,
//     createdAt: Date,
//     url: "..."
//   }
// ]
Pending comments only include unresolved threads from human reviewers. Bot comments are filtered out.
Get Automated Comments Fetch comments from bots (CI tools, linters, security scanners):
const botComments = await scm.getAutomatedComments(pr);
// Returns: AutomatedComment[]
// [
//   {
//     id: "comment-456",
//     botName: "codecov[bot]",
//     body: "Coverage decreased by 2%",
//     path: "src/utils.ts",
//     line: 15,
//     severity: "warning",  // "error" | "warning" | "info"
//     createdAt: Date,
//     url: "..."
//   }
// ]

Known Bot Authors

The plugin filters these bot accounts:
  • cursor[bot]
  • github-actions[bot]
  • codecov[bot]
  • sonarcloud[bot]
  • dependabot[bot]
  • renovate[bot]
  • codeclimate[bot]
  • deepsource-autofix[bot]
  • snyk-bot
  • lgtm-com[bot]

Merge Readiness

Get Mergeability Comprehensive merge readiness check:
const readiness = await scm.getMergeability(pr);
// Returns: MergeReadiness
// {
//   mergeable: true,        // Overall readiness
//   ciPassing: true,        // All CI checks passed
//   approved: true,         // Reviews approved
//   noConflicts: true,      // No merge conflicts
//   blockers: []            // List of blocking issues
// }
Blocker Examples When mergeable: false, the blockers array contains reasons:
blockers: [
  "CI is failing",
  "Changes requested in review",
  "Merge conflicts",
  "Branch is behind base branch",
  "PR is still a draft",
  "Review required",
  "Merge is blocked by branch protection",
  "Required checks are failing"
]
The plugin uses a fail-closed approach: if it can’t determine CI status or review state, it reports the PR as not ready to merge.

Usage Example

Auto-Merge Workflow

# agent-orchestrator.yaml
reactions:
  - event: ci_passed
    conditions:
      - type: review_approved
      - type: no_conflicts
    action: merge_pr
    merge_method: squash
This configuration automatically merges PRs when:
  1. All CI checks pass
  2. Reviews are approved
  3. No merge conflicts exist

Monitor PR Progress

# Check PR status
ao status session-id

# Output:
# PR #123: Fix login bug
# State: open
# CI: passing (3/3 checks)
# Reviews: approved
# Blockers: Branch is behind base branch

Handle CI Failures

reactions:
  - event: ci_failed
    action: notify
    message: "CI failed on PR {{pr.number}}: {{pr.url}}"

Troubleshooting

The GitHub CLI is not installed or not in PATH.Solution:
# macOS
brew install gh

# Linux
sudo apt install gh

# Verify
which gh
The plugin couldn’t retrieve CI check status.Common causes:
  • No CI checks configured for the repository
  • GitHub API timeout
  • Network connectivity issues
  • PR is closed/merged (no check data)
The plugin fails closed: reports CI as “failing” to prevent auto-merge.
This happens when all checks are skipped or neutral.The plugin only reports passing if at least one check actually passed (not all skipped).
GitHub’s reviewDecision field aggregates reviews. It’s none when:
  • No review is required by branch protection
  • All reviews are comments (not approvals or change requests)
  • Reviews were dismissed
The plugin filters bot comments using a predefined list of known bot authors.If a new bot is not filtered, add it to BOT_AUTHORS in the source code:
const BOT_AUTHORS = new Set([
  "cursor[bot]",
  "your-new-bot[bot]",  // Add here
  // ...
]);
GitHub branch protection rules are preventing the merge.Common protections:
  • Required reviews not met
  • Required status checks not passed
  • Branch not up to date with base
  • Signed commits required
Check repository settings → Branches → Branch protection rules.
GitHub is still computing merge state (happens for large PRs or complex conflicts).The plugin reports this as a blocker: “Merge status unknown (GitHub is computing)”Wait a few seconds and check again.

API Reference

SCM Interface Methods

detectPR(session, project)
  • Finds PR by branch name
  • Returns: PRInfo | null
getPRState(pr)
  • Returns: "open" | "closed" | "merged"
getPRSummary(pr)
  • Returns: { state, title, additions, deletions }
mergePR(pr, method)
  • Merges PR with specified method
  • Deletes branch automatically
  • Returns: void
closePR(pr)
  • Closes PR without merging
  • Returns: void
getCIChecks(pr)
  • Returns: CICheck[]
getCISummary(pr)
  • Returns: "passing" | "failing" | "pending" | "none"
getReviews(pr)
  • Returns: Review[]
getReviewDecision(pr)
  • Returns: "approved" | "changes_requested" | "pending" | "none"
getPendingComments(pr)
  • Returns: ReviewComment[] (human reviewers only)
getAutomatedComments(pr)
  • Returns: AutomatedComment[] (bots only)
getMergeability(pr)
  • Returns: MergeReadiness

Advanced Usage

Merge Methods

// Squash: All commits into one (default)
await scm.mergePR(pr, "squash");

// Merge: Create merge commit
await scm.mergePR(pr, "merge");

// Rebase: Rebase and fast-forward
await scm.mergePR(pr, "rebase");

CI Monitoring Loop

// Poll CI status until completion
let status = await scm.getCISummary(pr);
while (status === "pending") {
  await sleep(30_000);  // Wait 30 seconds
  status = await scm.getCISummary(pr);
}

if (status === "passing") {
  console.log("CI passed!");
} else if (status === "failing") {
  const checks = await scm.getCIChecks(pr);
  const failed = checks.filter(c => c.status === "failed");
  console.error(`CI failed: ${failed.map(c => c.name).join(", ")}`);
}

Review Comment Handling

// Check for pending human review comments
const pending = await scm.getPendingComments(pr);
if (pending.length > 0) {
  console.log(`${pending.length} unresolved comments`);
  for (const comment of pending) {
    console.log(`${comment.path}:${comment.line} - ${comment.author}`);
    console.log(comment.body);
  }
}

// Check for automated feedback
const automated = await scm.getAutomatedComments(pr);
const errors = automated.filter(c => c.severity === "error");
if (errors.length > 0) {
  console.log(`${errors.length} automated errors to address`);
}

Comprehensive Merge Check

const readiness = await scm.getMergeability(pr);

if (readiness.mergeable) {
  await scm.mergePR(pr, "squash");
  console.log("PR merged successfully!");
} else {
  console.log("PR not ready to merge:");
  for (const blocker of readiness.blockers) {
    console.log(`  - ${blocker}`);
  }
}

Integration with Other Plugins

With Tracker Plugin

The SCM and tracker plugins work together:
  1. Tracker creates issue and generates prompt
  2. Agent writes code and creates PR
  3. SCM monitors CI and reviews
  4. Tracker updates issue when PR merges
reactions:
  - event: pr_created
    action: run
    command: |
      # Link PR to Linear issue
      echo "PR created: $PR_URL" >> $ISSUE_COMMENT
  
  - event: pr_merged
    action: run
    command: |
      # Close issue
      ao tracker update $ISSUE_ID --state closed --comment "Fixed in PR $PR_URL"

With Notifier Plugin

Send notifications based on PR events:
reactions:
  - event: ci_failed
    action: notify
    channel: slack
    message: |
      CI failed on PR #{{pr.number}}
      {{pr.url}}
      
      Failed checks:
      {{#each failedChecks}}
      - {{name}}: {{url}}
      {{/each}}
  
  - event: changes_requested
    action: notify
    channel: desktop
    message: "Changes requested on PR #{{pr.number}}"

Performance Considerations

API Calls

The plugin makes multiple API calls per operation:
  • getMergeability: 3-4 calls (PR state, CI, reviews, merge status)
  • getCIChecks: 1 call
  • getReviews: 1 call
  • getPendingComments: 1 call (GraphQL)

Rate Limits

GitHub API rate limits (authenticated):
  • REST API: 5,000 requests/hour
  • GraphQL API: 5,000 points/hour
The gh CLI handles authentication and rate limiting automatically.

Caching

The plugin does not cache responses. The orchestrator should implement caching if polling frequently.

Security Considerations

  • All GitHub API calls are made through the gh CLI (no direct token handling)
  • Commands are executed with execFile (not exec) to prevent shell injection
  • 30-second timeout prevents hanging on network issues
  • Repository format is validated before use
  • Fail-closed approach: unknown states are treated as blocking

Source Code

Source: packages/plugins/scm-github/src/index.ts

Build docs developers (and LLMs) love