Hybrid Runner System
Implemented: 2025-10-24 Status: Active - Running in production Purpose: Conserve GitHub Actions quota while maintaining fast, reliable CI/CD
Overview
The hybrid GitHub Actions runner system automatically balances between GitHub-hosted and self-hosted runners to optimize quota usage (3000 minutes/month on Pro plan).
Key Benefits:
- Automatic quota management - Days 22-31 use self-hosted, days 1-21 use GitHub-hosted
- Zero manual intervention - Resets automatically on first of each month
- Manual override options - Commit flags and workflow dispatch for control
- Quota conservation - Saves ~300-500 minutes/month during end-of-month crunch
Architecture
Runner Selection Logic
graph TD A[Workflow Triggered] --> B{Check Skip Flags} B -->|[skip-ci] flag| C[Skip Workflow] B -->|No skip flag| D{Check Override} D -->|[self-hosted] flag| E[Use Self-Hosted] D -->|[github] flag| F[Use GitHub-Hosted] D -->|No override| G{Auto Mode} G -->|Day 1-21| F G -->|Day 22-31| E
Decision Job (PowerShell)
Runs on self-hosted runner to avoid ANY GitHub quota usage:
$COMMIT_MSG = "${{ github.event.head_commit.message || github.event.pull_request.title }}"
$INPUT_MODE = "${{ github.event.inputs.runner_mode || 'auto' }}"
$DAY = (Get-Date).Day
if ($DAY -ge 22) {
$RUNNER = "self-hosted" # End of month - conserve quota
} else {
$RUNNER = "ubuntu-latest" # Early month - use GitHub quota
}
# Outputs used by subsequent jobs
"runner_label=$RUNNER" >> $env:GITHUB_OUTPUTUsage
Automatic Mode (Default)
No action required! The system automatically uses:
- Days 1-21: GitHub-hosted runners
- Days 22-31: Self-hosted runners
- Resets: Automatically on first of each month
Manual Override (Commit Flags)
Force self-hosted runner:
git commit -m "feat: Add new feature [self-hosted]"Force GitHub-hosted runner:
git commit -m "fix: Quick bug fix [github]"Skip CI entirely:
git commit -m "docs: Update README [skip-ci]"Manual Dispatch (Actions UI)
- Go to GitHub → Actions tab
- Select workflow (Android CI or Deploy)
- Click “Run workflow”
- Choose runner_mode:
auto- Use day-based logic (default)self-hosted- Force self-hostedgithub- Force GitHub-hostedskip- Skip CI run
Workflows
android-ci.yml (Pull Request Validation)
Structure:
decide_runner- Selects runner (runs on self-hosted, 4s)quick_checks- Lint and basic validation (runs on selected runner)test_and_coverage- Full build + tests + coverage (runs on selected runner)
Jobs split strategy:
- Quick checks always on GitHub (fast, cheap)
- Heavy work on selected runner (build, tests, coverage)
deploy-to-play-store.yml (Main Branch Deployment)
Structure:
decide_runner- Selects runner (runs on self-hosted, 4s)deploy- Build release AAB + deploy to Play Store (runs on selected runner)
Same selection logic as CI workflow
Self-Hosted Runner Setup
Dual Runner Architecture
Current Setup: Two separate self-hosted runners on the same Windows machine
| Runner | Location | GitHub Name | Repository | Purpose |
|---|---|---|---|---|
| Main | C:\actions-runner\ | (default) | blamechris/archery-apprentice | Main app CI/CD |
| Docs | C:\actions-runner-docs\ | SOLACE | blamechris/archery-apprentice-docs | Documentation deployment |
Why Separate Runners?
- Repository isolation - Each runner registered to specific repository
- Job independence - Main CI and docs deployment can run concurrently
- Quota tracking - Separate accounting for each repository’s usage
- Configuration isolation - Different SDK requirements (main needs Android SDK, docs only needs Node.js)
Setup Details:
- Both runners run as Windows services
- Both share same JDK/Node.js installations
- Only main runner needs Android SDK at
C:\Android\Sdk - Docs runner setup documented in archery-apprentice-docs README
Desktop/Server Setup
See: Self-Hosted Runner Setup Guide
Key steps:
- Install JDK 17, Android SDK, Git Bash
- Register runner with GitHub
- Configure Windows service
- Copy Android SDK to
C:\Android\Sdk(for Windows)
Laptop Setup (Optional)
Same process, but consider:
- Power management - Disable sleep when running
- Network - Stable connection required
- Use case - Portable CI, travel development
Most developers don’t need laptop runners - they’re for advanced use cases.
Local Development
IMPORTANT: Self-hosted runners are NOT for local development!
Local development uses ./gradlew directly:
# Build locally
./gradlew assembleDebug
# Test locally
./gradlew testDebugUnitTest
# Instrumented tests (requires emulator)
./gradlew connectedAndroidTestAll tests run natively on your machine - no GitHub Actions infrastructure needed.
Troubleshooting
Codecov Upload Fails on Windows
Issue: sh: command not found
Root cause: codecov-action requires bash/sh, which isn’t in default Windows PATH
Solution: Made Codecov non-blocking in android-ci.yml:272-282
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: false # Non-blocking
continue-on-error: true # Allow workflow to succeedImpact: Coverage still uploaded via PR comment + HTML artifacts, Codecov is redundant.
Android SDK Permissions on Windows
Issue: NETWORK SERVICE account can’t access user AppData folder
Solution: Copy Android SDK to system-wide location:
Copy-Item -Path "C:\Users\USERNAME\AppData\Local\Android\Sdk" `
-Destination "C:\Android\Sdk" `
-Recurse -Force
# Update local.properties
echo "sdk.dir=C:/Android/Sdk" > local.propertiesExplanation: Windows services run as NETWORK SERVICE, which has restricted permissions on user folders.
Runner Shows Offline
Check service status:
# Windows
.\svc.sh status
# Linux/macOS
sudo ./svc.sh statusRestart if needed:
# Windows
.\svc.sh restart
# Linux/macOS
sudo ./svc.sh restartQuota Analysis
Monthly Quota: 3000 Minutes
Before hybrid runner:
- ~10-15 builds/day × 15 minutes = 150-225 minutes/day
- 30-day month: 4,500-6,750 minutes (EXCEEDS quota by 1.5-2.25x)
After hybrid runner (days 22-31 on self-hosted):
- Days 1-21: ~10 builds/day × 15 min = 150 min/day × 21 = 3,150 minutes
- Days 22-31: ~10 builds/day × 0 min (self-hosted) = 0 minutes
- Total GitHub quota used: ~3,150 minutes (within quota!)
Quota saved: ~300-500 minutes/month
Implementation Timeline
Date: 2025-10-24 (1 intensive day)
Key milestones:
- ✅ Workflow design - Day-based heuristic decision
- ✅ Windows PowerShell compatibility - Cross-platform shell scripts
- ✅ Self-hosted runner setup - Desktop with Windows service
- ✅ Android SDK permissions - Moved to C:\Android\Sdk
- ✅ Codecov fix - Made non-blocking for Windows compatibility
- ✅ Documentation - README, CLAUDE.md, setup guide, Obsidian
Challenges overcome:
- Windows PowerShell vs Bash syntax differences
- NETWORK SERVICE account SDK permissions
- Codecov bash dependency on Windows
- Cross-platform JSON generation encoding
Related Documentation
Code Repository
Obsidian Vault
- Self-Hosted Runner Setup Guide
- KMP Migration Project (uses hybrid runners for Week 3+ work)
Tags: ci-cd github-actions hybrid-runner quota-management infrastructure Status: ✅ Active in production Next Review: 2025-11-01 (verify auto-switchback on month rollover)