README
ΒΆ
π Abgleich
Abgleich is a background worker that enables auto-merge or merges ready Dependabot pull requests and keeps auto-merge branches current across a GitHub account's opted-in repositories.
π― Background
An open, non-draft pull request is stale when its branch is missing commits from its base branch. Age does not affect eligibility. Only pull requests with auto-merge enabled and both branches in the same discovered repository are rebased. Public and private repositories visible to the owner's token are discovered on each run. Archived, disabled, foreign, and repositories without Allow auto-merge enabled are skipped entirely. That repository setting is the opt-in for the worker.
For Dependabot PRs, the worker also enables squash auto-merge while requirements
are pending, or requests a squash merge when GitHub reports the PR ready. It
identifies Dependabot by the PR author dependabot[bot], independent of branch
names. Draft, closed, and fork PRs are excluded. Other authors must enable
auto-merge themselves; the worker only rebases their eligible branches.
π Quickstart
Run these steps from the repository root.
β Prerequisites
- Go matching go.mod.
- Git with GitHub SSH access for the
bin/submodule. - Ruby with Bundler for dependency setup and the Cucumber harness.
- Make, plus
curlfor the readiness check below.
ποΈ Bootstrap and build
Initialize the shared tooling, install dependencies, and build the worker:
git submodule sync
git submodule update --init
make dep
make build
βΆοΈ Configure and start
Save the example in Configuration below as config.yaml, choosing the GitHub
personal account to manage. Supply that account's token with the permissions described
under Credentials. The example reads it from GITHUB_TOKEN.
If GITHUB_TOKEN is already exported in your shell, start the worker directly:
./abgleich server -config file:config.yaml
The github.token: env:GITHUB_TOKEN setting tells the worker to read the exported
environment variable once at startup.
If you need to set the token first, you can use this optional hidden prompt in Bash or Zsh, then run the startup command above:
printf 'GitHub token: '
read -r -s GITHUB_TOKEN
printf '\n'
export GITHUB_TOKEN
The worker begins checking the account's repositories immediately. Leave it running, and use another terminal to verify readiness:
curl --fail --silent --show-error http://localhost:11000/abgleich/readyz
An HTTP 200 response with body SERVING confirms process readiness. Check the
worker's structured logs for GitHub check outcomes; readiness is independent of
whether a branch check succeeds.
π Server
The worker runs immediately on startup and, by default, every fifteen minutes
thereafter. It discovers all owned repositories before processing them serially.
For each repository, it lists open pull requests through go-github, compares each eligible
branch with its base, and asks GitHub to rebase stale branches using the GraphQL
updatePullRequestBranch
mutation with updateMethod: REBASE. This is the server-side operation used by
GitHub's update-with-rebase UI and CLI. GitHub performs the branch update.
Before listing PRs, the worker reads the full repository settings; GitHub's
repository list omits merge settings. Dependabot automation uses GraphQL
enablePullRequestAutoMerge
with mergeMethod: SQUASH, or the REST merge API with merge_method: squash
and the expected head SHA. Immediate merges require mergeable: true and
mergeable_state: clean; unknown or failing check states never trigger an
immediate merge. GitHub still enforces checks, reviews, and repository rules.
The worker does not approve reviews or request rule bypasses.
Each sweep attempts at most one mutation per PR. After enabling auto-merge, a later sweep can rebase a stale branch. If squash merging is disabled, the Dependabot action fails with an operator-visible error instead of choosing another merge method. Other authors' auto-merge PRs retain their selected method.
Pull requests are rechecked immediately before each mutation. Rebases include
expectedHeadOid and immediate merges include sha, so concurrent branch changes
reject those requests and are discovered on the next run. Conflicts reported by GitHub fail the branch and
require an author to repair it. One failed branch does not prevent other
eligible branches or repositories from being processed. After a timeout, the
next scheduled sweep starts with fresh repository discovery.
Discovery validates that the authenticated personal account matches github.owner.
All repository pages must succeed before processing begins. Pull request pagination
also finishes before any branches in that repository are updated.
The process needs network access to the GitHub REST and GraphQL APIs. It does not clone repositories, execute Git, or need a writable checkout directory.
βοΈ Configuration
The github and health blocks are required. Both health.duration and
health.timeout must be positive durations; they have no defaults. This example
also includes shared go-service settings for identifiers, logging, metrics, and
the HTTP listener.
Save it as config.yaml and adjust github.owner:
environment: production
github:
client:
timeout: 1m
retry:
attempts: 2
backoff: 100ms
owner: alexfalkowski
token: env:GITHUB_TOKEN
scheduler:
interval: 15m
timeout: 5m
health:
duration: 1s
timeout: 1s
id:
kind: uuid
telemetry:
logger:
kind: json
level: info
metrics:
kind: prometheus
transport:
http:
address: tcp://:11000
timeout: 5s
Set github.owner and github.token. The schedule and
HTTP client settings are optional:
| Setting | Default | Purpose |
|---|---|---|
scheduler.interval |
15m |
Time between owner-wide sweeps. |
scheduler.timeout |
5m |
Deadline for a complete owner-wide sweep. |
github.client.timeout |
1m |
Deadline for an individual HTTP request. |
github.url |
https://api.github.com/ |
REST API origin; GraphQL uses /graphql on the same origin. |
Omitted or zero scheduler durations and GitHub client timeouts use the defaults above. Negative schedule durations are invalid.
π HTTP transport
GitHub HTTP transport settings live under github.client: timeout, retries,
circuit breaking, and TLS. Retries and circuit breaking remain disabled unless configured.
Configured retries apply only to safe HTTP methods. Rebase, auto-merge, and merge
mutations are never retried by the HTTP transport; a later scheduled run reads
current GitHub state before deciding whether another action is needed.
π¦ API usage
One shared go-github client processes repositories and pull requests serially. The fifteen-minute interval limits polling volume; there is no additional rate-limit library or configuration. If GitHub reports throttling, the current sweep stops instead of continuing through other repositories. go-github retains its built-in rate-limit checks, and a later scheduled run reads current state again.
Personal access tokens share the account's API budget with other automation.
Monitor repeated rate-limit failures and increase scheduler.interval if needed.
π Credentials
Use a personal access token belonging to github.owner, covering all repositories
to manage, including private and newly created repositories. A fine-grained token
needs Pull requests: read and write and Contents: read and write.
Enable Allow auto-merge on each repository to opt in, and Allow squash
merging for Dependabot automation. Required checks and reviews must still pass;
repository rules can reject a rebase or merge. Repositories outside the token's
access cannot be discovered.
A source such as
file:/run/secrets/github-token supports mounted secrets. Both REST and GraphQL
requests use the same authenticated client. go-github restricts authentication
to the configured API origin.
[!IMPORTANT] Credentials are read once at startup. Missing or empty tokens prevent startup. Restart the worker after changing or restoring its token.
π©Ί Health
The service exposes HTTP observability endpoints:
| Endpoint | Behavior |
|---|---|
/abgleich/healthz |
Shared external connectivity check. |
/abgleich/readyz |
Process readiness, independent of scheduled job outcomes. |
/abgleich/livez |
Process liveness, independent of GitHub failures. |
/abgleich/metrics |
Prometheus metrics when that exporter is configured. |
Job failures, conflicts, and timeouts do not affect these health checks. Infraops probes the private readiness and liveness endpoints directly, including for workers without a Service or Ingress.
health.duration controls how frequently health state is sampled.
health.timeout bounds the shared connectivity check; it is independent of the
scheduled job's run timeout.
π Telemetry
Each completed run produces one log entry with job, duration, interval, and
overrun. The auto-merge job uses job: automerge.
| Outcome | Log level | Details |
|---|---|---|
| Success | info |
The check completed within its interval. |
| Successful overrun | warn |
The check succeeded but exceeded its interval. |
| Failure | error |
Includes the error, even when the check also overran. |
Accepted rebase requests, enabled auto-merges, and confirmed merges identify the
repository, pull request, and branch. Their messages are requested pull request rebase,
enabled pull request auto-merge, and merged pull request, respectively. Failures
include repository names and pull request numbers where available. GraphQL errors
are failures even when the HTTP response is 200. A failed branch contributes to
the run's failure outcome while other eligible branches continue to be processed.
- Timeouts: the default run timeout cancels active work after five minutes.
- Overruns: work that exceeds its interval sets
overruntotruewhen it finishes, preserving the handler's success or failure result. - Overlap: each tick attempts to start work. Attempts while the previous run is active are ignored; missed attempts are not queued.
Scheduled job monitoring uses structured logs. Configure collection and alerts for repeated failures, exceeded intervals, and missing completed runs in your log backend. The scheduler does not publish custom job metrics. Standard go-service HTTP and runtime telemetry remains available through its configured exporters.
π¦ Deployment
- Instances: run as a long-lived service with one active instance. Scheduling limits apply within a process; the expected head OID guards updates from other instances and CI.
- Restarts: use liveness for restart decisions so a GitHub outage does not cause restart loops.
- Shutdown: active work is cancelled, and the worker waits for it to stop.
π§― Recovery
No local checkpoint or database is needed. Every run reconciles current GitHub state. After a crash, timeout, or ambiguous mutation response, a fresh run determines whether the remote branch still needs updating. GitHub can complete an accepted update after the caller stops waiting; retries always compare current remote state before requesting another rebase. The standard service container image is sufficient.
π§© Design
| Package | Responsibility |
|---|---|
automerge |
Repository opt-in, PR eligibility, Dependabot merge and rebase decisions, and job registration. |
github |
Owner-wide repository discovery, identity checks, authenticated API access, pagination, and response validation. |
scheduler |
Independent schedules, timeouts, overlap protection, outcome logs, and job lifecycle. |
cmd |
Composition of package modules. |
Dependencies follow the responsibilities:
flowchart LR
automerge --> github
automerge --> scheduler
automerge.Reconciler.Reconcile asks github.Service for all repositories, then uses
that service for repository settings, PR reads, identity checks, comparisons,
auto-merge enablement, and guarded rebase and merge requests. github.Repository
and pull request types alias SDK snapshots;
they contain no service behavior. The service uses an injected github.Client,
which aliases the go-github client. The GitHub module constructs it once through
github.NewClient, with shared HTTP options and lifecycle dependencies injected.
SDK imports stay inside internal/github.
Logging is composed around operations. github.NewService wraps the API service
with a logging decorator to report accepted actions after response validation.
Scheduler.Register wraps each handler to report its outcome and overruns
within the job's timeout.
ποΈ Job registration
Each package registers construction in its own module. The
auto-merge module constructs the reconciler and
registers Reconcile as the automerge job. Other feature packages can register
their own named handlers and schedules through Scheduler.Register. Registration
finishes before startup, and duplicate names prevent application startup.
Scheduler has no feature-package dependencies.
The scheduler module connects Scheduler.Start
and Scheduler.Stop to the service lifecycle. Each
job has its own ticker, run timeout, cancellation,
and outcome logs. Jobs run independently and use a nonblocking lock to admit
one run per job at a time. Shutdown cancels all jobs before waiting for active
work. Handlers must observe cancellation and return their complete outcome.
Health checks are independent of the scheduler.
GitHub API access uses go-github v93.
π οΈ Development
If you would like to contribute, here is how you can get started.
ποΈ Structure
The project follows the structure in golang-standards/project-layout.
βοΈ Style
This project favours the Uber Go Style Guide
π Setup
After completing the quickstart's bootstrap steps, run the auto-merge features:
make features feature=features/automerge
Use make help to discover the other development commands.
Nonnative starts the worker and a local fake GitHub REST and GraphQL API:
- Rebasing covers eligibility, pagination, conflicts, and concurrent changes.
- Dependabot covers repository opt-in, enabling auto-merge, guarded squash merges, exclusions, and failure recovery.
- Repositories covers owner-wide discovery, private repositories, failure isolation, throttling, and timeout recovery.
- Recovery covers GitHub failures, retries, and ambiguous mutation responses.
- Scheduling covers timeouts, overlap prevention, and restart recovery.
- Credentials covers startup token
validation and replacement. Run it with
make features feature=features/config/config.feature.
Failure scenarios also verify that process probes remain healthy. Tests use an accelerated schedule and disposable credentials; they do not modify GitHub. See CI for the build and release workflow.
π Changes
Release history is available in GitHub releases.
Documentation
ΒΆ
There is no documentation for this package.
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
internal
|
|
|
automerge
Package automerge enables auto-merge or merges ready Dependabot PRs and keeps eligible branches current in opted-in repositories.
|
Package automerge enables auto-merge or merges ready Dependabot PRs and keeps eligible branches current in opted-in repositories. |
|
github
Package github provides owner-wide discovery and repository access through Service, using go-github for REST reads and merges, and GraphQL rebase and auto-merge requests.
|
Package github provides owner-wide discovery and repository access through Service, using go-github for REST reads and merges, and GraphQL rebase and auto-merge requests. |
|
scheduler
Package scheduler runs independently scheduled jobs and logs their outcomes and durations.
|
Package scheduler runs independently scheduled jobs and logs their outcomes and durations. |