Skip to content

Run Linux CI jobs on local runners when PM_LOCAL_RUNNER=on, and cancel superseded PR runs - #247

Open
unbraind wants to merge 1 commit into
mainfrom
ci/local-runners
Open

unbraind wants to merge 1 commit into
mainfrom
ci/local-runners

Conversation

@unbraind

@unbraind unbraind commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

Part of moving the fleet's CI off GitHub's shared 20-job pool. In 48 h the fleet used about 60,800 runner wall-minutes, about 21 concurrent runs against GitHub's limit of 20.

  • Linux jobs (2): runs-on is an expression. With the repository variable PM_LOCAL_RUNNER=on (set), they run on the fleet's local, ephemeral runners ([self-hosted, pm-local]): each a fresh container that takes one job, capped in CPU, memory and pids, with no Docker socket and no host mounts.
  • Fork pull requests always use ubuntu-latest, and workflows from outside contributors' fork PRs now need approval (all_external_contributors).
  • Concurrency: a new push to a pull request cancels that PR's earlier CI run. Pushes to main and manual runs never wait for or cancel each other.
  • Fallback: setting the variable to off puts every job back on GitHub-hosted runners. The controller does this by itself if no LAN host is reachable for 10 minutes.

Required check names are unchanged. Pilot: unbraind/pm-memory#73.

Summary by Sourcery

Enable optional local execution for Linux CI jobs and prevent outdated pull request runs from consuming runner capacity.

New Features:

  • Allow Linux CI jobs to run on fleet-local ephemeral runners when enabled, while keeping fork pull requests on GitHub-hosted runners.

Enhancements:

  • Cancel superseded pull request CI runs while keeping main-branch and manual runs independent.
  • Retain GitHub-hosted runners as the fallback when local execution is disabled or unavailable.

Summary by cubic

Offloads the two Linux CI jobs to the fleet's local ephemeral runners when the PM_LOCAL_RUNNER repository variable is on, and cancels superseded pull request runs so CI no longer saturates GitHub's 20-job pool.

Written for commit ffe4c17. Summary will update on new commits.

View guided diff Turn on auto-fix

…l superseded PR runs

The fleet's CI saturated GitHub's 20 concurrent jobs: about 60,800 runner
wall-minutes in 48 h. With the repository variable PM_LOCAL_RUNNER=on, the
Linux jobs in this workflow (2) run on the fleet's local ephemeral runners
([self-hosted, pm-local]): one fresh, resource-capped container per job.
Fork pull requests always stay on ubuntu-latest. A new push to a pull request
cancels that PR's earlier run; pushes to main and manual runs never wait for
or cancel each other. Required check names are unchanged.
@sourcery-ai

sourcery-ai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Configuration

Route both Linux jobs to local runners only when enabled and safe for same-repository pull requests, while keeping the GitHub-hosted fallback.

  • .github/workflows/ci.yml L22, L82

Cancel superseded pull-request runs without coupling pushes to main or manual runs, and document the runner and concurrency behavior.

  • .github/workflows/ci.yml L12-19

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Nov 14, 2026

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 3904e5e0-408a-4c0a-896c-1f94b590b3f1
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@greptile-apps

greptile-apps Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge.

Summary

The PR adds optional local runners for Linux CI jobs and cancels earlier runs for the same pull request.

  • Eligible CI jobs can run on the local runner pool.
  • New pull-request runs cancel older runs for the same pull request.

No actionable issues found.

Reviews (1) · Last reviewed commit: "Run Linux CI jobs on local runners when ..." · Reviewed by Greptile

@sourcery-ai sourcery-ai 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.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path=".github/workflows/ci.yml" line_range="22" />
<code_context>
 jobs:
   test:
-    runs-on: ubuntu-latest
+    runs-on: ${{ (vars.PM_LOCAL_RUNNER == 'on' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","pm-local"]') || 'ubuntu-latest' }}
     strategy:
       fail-fast: false
</code_context>
<issue_to_address>
**Fork code reaches local runners**

When PM_LOCAL_RUNNER is `on` and an external contributor's fork PR has its workflow run approved, a fork author can edit this PR-triggered workflow to remove the fork check or add a job targeting `self-hosted`; the edited workflow then runs PR-controlled code on a local runner, exposing its job token and runner-reachable resources.

Keep PR-controlled workflows off local runners, or enforce local-runner eligibility through a policy that PR authors cannot change.

Also at `.github/workflows/ci.yml:82`.
</issue_to_address>

### Comment 2
<location path=".github/workflows/ci.yml" line_range="17-18" />
<code_context>
+# A new push to a pull request supersedes its earlier CI run: cancel it instead of letting both hold runners.
+# Pushes to main and manual runs get a group of their own, so they never wait for or cancel each other.
+concurrency:
+  group: ci-${{ github.event.pull_request.number && format('pr-{0}', github.event.pull_request.number) || github.run_id }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
 jobs:
</code_context>
<issue_to_address>
**Older PR run cancels latest**

When two pushes to the same PR are processed out of order and the older push's run enters the concurrency group last, gitHub does not guarantee that runs enter a concurrency group in push order, so an older push's run can enter after a newer push's run and cancel it. The older SHA then gets the remaining CI result while the PR's current commit has no successful required checks.

Before allowing an event's run to cancel another, verify that its head SHA is still the PR's current head, or otherwise serialize runs in commit order.
</issue_to_address>

### Comment 3
<location path=".github/workflows/ci.yml" line_range="22" />
<code_context>
 jobs:
   test:
-    runs-on: ubuntu-latest
+    runs-on: ${{ (vars.PM_LOCAL_RUNNER == 'on' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","pm-local"]') || 'ubuntu-latest' }}
     strategy:
       fail-fast: false
</code_context>
<issue_to_address>
**Queued checks never fall back**

When a local runner becomes unavailable after jobs are queued and the controller turns `PM_LOCAL_RUNNER` off, `runs-on` has already resolved to `[self-hosted, pm-local]`, so changing the variable does not reroute those jobs to `ubuntu-latest`; they stay queued until a matching runner returns or the runs are canceled, leaving the check blocked.

When disabling the local runner, cancel and recreate queued jobs with `runs-on: ubuntu-latest` rather than relying on the resolved expression to change.

Also at `.github/workflows/ci.yml:82`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 3 findings to address first, and when enabled, code from same-repository pull requests runs on self-hosted runners; if runner isolation or access is broader than intended, that code could expose runner-accessible data or affect the runner fleet. Reverting stops future runs there, but cannot undo any exposure or damage that already occurred.

Blocking findings: .github/workflows/ci.yml:22, .github/workflows/ci.yml:18, .github/workflows/ci.yml:22


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
runs-on: ${{ (vars.PM_LOCAL_RUNNER == 'on' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","pm-local"]') || 'ubuntu-latest' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Fork code reaches local runners

When PM_LOCAL_RUNNER is on and an external contributor's fork PR has its workflow run approved, a fork author can edit this PR-triggered workflow to remove the fork check or add a job targeting self-hosted; the edited workflow then runs PR-controlled code on a local runner, exposing its job token and runner-reachable resources.

Keep PR-controlled workflows off local runners, or enforce local-runner eligibility through a policy that PR authors cannot change.

Also at .github/workflows/ci.yml:82.

Prompt for AI agents
In `.github/workflows/ci.yml` at line 22:

**Fork code reaches local runners**

When PM_LOCAL_RUNNER is `on` and an external contributor's fork PR has its workflow run approved, a fork author can edit this PR-triggered workflow to remove the fork check or add a job targeting `self-hosted`; the edited workflow then runs PR-controlled code on a local runner, exposing its job token and runner-reachable resources.

Keep PR-controlled workflows off local runners, or enforce local-runner eligibility through a policy that PR authors cannot change.

Also at `.github/workflows/ci.yml:82`.

Comment thread .github/workflows/ci.yml
Comment on lines +17 to +18
group: ci-${{ github.event.pull_request.number && format('pr-{0}', github.event.pull_request.number) || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Older PR run cancels latest

When two pushes to the same PR are processed out of order and the older push's run enters the concurrency group last, gitHub does not guarantee that runs enter a concurrency group in push order, so an older push's run can enter after a newer push's run and cancel it. The older SHA then gets the remaining CI result while the PR's current commit has no successful required checks.

Before allowing an event's run to cancel another, verify that its head SHA is still the PR's current head, or otherwise serialize runs in commit order.

Prompt for AI agents
In `.github/workflows/ci.yml` at lines 17-18:

**Older PR run cancels latest**

When two pushes to the same PR are processed out of order and the older push's run enters the concurrency group last, gitHub does not guarantee that runs enter a concurrency group in push order, so an older push's run can enter after a newer push's run and cancel it. The older SHA then gets the remaining CI result while the PR's current commit has no successful required checks.

Before allowing an event's run to cancel another, verify that its head SHA is still the PR's current head, or otherwise serialize runs in commit order.

Comment thread .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
runs-on: ${{ (vars.PM_LOCAL_RUNNER == 'on' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","pm-local"]') || 'ubuntu-latest' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Queued checks never fall back

When a local runner becomes unavailable after jobs are queued and the controller turns PM_LOCAL_RUNNER off, runs-on has already resolved to [self-hosted, pm-local], so changing the variable does not reroute those jobs to ubuntu-latest; they stay queued until a matching runner returns or the runs are canceled, leaving the check blocked.

When disabling the local runner, cancel and recreate queued jobs with runs-on: ubuntu-latest rather than relying on the resolved expression to change.

Also at .github/workflows/ci.yml:82.

Prompt for AI agents
In `.github/workflows/ci.yml` at line 22:

**Queued checks never fall back**

When a local runner becomes unavailable after jobs are queued and the controller turns `PM_LOCAL_RUNNER` off, `runs-on` has already resolved to `[self-hosted, pm-local]`, so changing the variable does not reroute those jobs to `ubuntu-latest`; they stay queued until a matching runner returns or the runs are canceled, leaving the check blocked.

When disabling the local runner, cancel and recreate queued jobs with `runs-on: ubuntu-latest` rather than relying on the resolved expression to change.

Also at `.github/workflows/ci.yml:82`.

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