ghacron
GitHub Actions schedule events (cron triggers) have a known issue where delays of over an hour can occur. ghacron is a Go service that reads annotations from workflow files and fires workflow_dispatch events on time.
How It Works
- Add annotations like
# ghacron: "0 8 * * *" to your workflow files
- The service scans repositories every 5 minutes and detects annotations
- Fires
workflow_dispatch according to the cron expression
- State is persisted via GitHub Actions Variables (no PVC required)
on:
# ghacron: "0 8 * * *"
workflow_dispatch:
workflow_dispatch: must be included under on:
- Multiple annotations per file are supported
- Cron expressions use the standard 5-field format (minute hour day month weekday)
Requirements
- Go 1.25 or later
- GitHub App (App ID + Private Key)
- Required permissions:
contents: read, actions: write, variables: write, metadata: read
Usage
./ghacron [options]
| Flag |
Default |
Description |
-config |
ghacron.yaml |
Path to config file |
-version |
— |
Show version and exit |
The default config path can also be overridden via the GHACRON_CONFIG environment variable.
# Binary
GH_APP_ID=123456 GH_APP_PRIVATE_KEY="$(cat key.pem)" ./ghacron
# Binary with custom config
./ghacron -config /etc/ghacron/ghacron.yaml
# Docker
docker run -e GH_APP_ID=123456 -e GH_APP_PRIVATE_KEY="$(cat key.pem)" ghcr.io/korosuke613/ghacron
# Docker with custom config
docker run -v ./ghacron.yaml:/app/ghacron.yaml \
-e GH_APP_ID=123456 -e GH_APP_PRIVATE_KEY="$(cat key.pem)" ghcr.io/korosuke613/ghacron
Configuration
The config file is optional. Without it, ghacron runs with built-in defaults and environment variables (GH_APP_ID, GH_APP_PRIVATE_KEY).
Config file example:
github:
app_id: ${GH_APP_ID}
private_key: "${GH_APP_PRIVATE_KEY}"
reconcile:
interval_minutes: 5
duplicate_guard_seconds: 60
dry_run: false
timezone: "Asia/Tokyo" # IANA timezone for cron schedule evaluation
log:
level: "info"
format: "json" # "json" or "text"
webapi:
enabled: true
host: "0.0.0.0"
port: 8080
API Endpoints
The web API server is enabled by default on port 8080. All responses are JSON.
GET /healthz
Health check for liveness probes.
{"status": "ok"}
GET /status
Service status including uptime and reconciliation state.
{
"uptime_seconds": 3600.5,
"registered_jobs": 3,
"last_reconcile": "2026-02-24T09:00:00Z"
}
GET /jobs
List of registered cron jobs with next scheduled run time.
[
{
"owner": "myorg",
"repo": "myrepo",
"workflow_file": "ci.yml",
"cron_expr": "0 8 * * *",
"next_run": "2026-02-25T08:00:00Z"
}
]
GET /config
Public configuration (credentials are not exposed).
{
"github": {"app_id": 123456},
"reconcile": {"interval_minutes": 5, "duplicate_guard_seconds": 60, "dry_run": false},
"log": {"level": "info", "format": "json"},
"webapi": {"enabled": true, "host": "0.0.0.0", "port": 8080}
}
Docker
# Build
docker build -t ghacron .
# Run (env vars only, no config file needed)
docker run -e GH_APP_ID=123456 -e GH_APP_PRIVATE_KEY="$(cat key.pem)" ghacron
# Run with custom config file
docker run -v ./ghacron.yaml:/app/ghacron.yaml \
-e GH_APP_ID=123456 -e GH_APP_PRIVATE_KEY="$(cat key.pem)" ghacron
Kubernetes Deployment
containers:
- name: ghacron
image: ghcr.io/korosuke613/ghacron:latest
env:
- name: GH_APP_ID
value: "123456"
- name: GH_APP_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: ghacron-secrets
key: private-key
volumeMounts:
- name: config
mountPath: /app/ghacron.yaml
subPath: ghacron.yaml
volumes:
- name: config
configMap:
name: ghacron-config
To use a custom config, create a ConfigMap:
kubectl create configmap ghacron-config --from-file=ghacron.yaml
Development
# Build
go build -ldflags="-s -w -X main.version=$(git describe --tags --always)" -o ghacron main.go
# Run (dry-run)
GH_APP_ID=123456 GH_APP_PRIVATE_KEY="$(cat key.pem)" ./ghacron
# Test
go test ./...
Release Strategy
Releases are triggered exclusively by GitHub Release events — pushing to main does not publish any artifacts.
- Create a GitHub Release (manually or via
/generate-release skill in Claude Code)
- The
release.yml workflow automatically:
- Builds Go binaries for linux/amd64 and linux/arm64 via GoReleaser
- Builds and pushes multi-arch Docker images to
ghcr.io/korosuke613/ghacron
Tagging rules:
| Release type |
Example tag |
Docker tags |
latest |
| Stable |
v1.0.0 |
1.0.0, 1.0, latest |
Yes |
| Release candidate |
v1.0.0-rc.1 |
1.0.0-rc.1 |
No |
Architecture
ghacron/
├── main.go # Entry point
├── config/ # Configuration management
├── github/ # GitHub App authentication & API client
├── scanner/ # Workflow scanning & annotation parsing
├── scheduler/ # Cron job management & reconciliation
├── api/ # Health/status API
├── Dockerfile
└── README.md
References
- cronium — A prior implementation with a similar approach
License
MIT License