{"id":49821581,"url":"https://github.com/devops-ia/pr-generator","last_synced_at":"2026-05-23T23:01:56.584Z","repository":{"id":346765417,"uuid":"1191745646","full_name":"devops-ia/pr-generator","owner":"devops-ia","description":"Image for pr-generator","archived":false,"fork":false,"pushed_at":"2026-05-01T09:45:52.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-13T11:42:56.243Z","etag":null,"topics":["bitbucket","docker","github","helm","kubernetes"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devops-ia.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-25T14:47:17.000Z","updated_at":"2026-05-01T09:45:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/devops-ia/pr-generator","commit_stats":null,"previous_names":["devops-ia/pr-generator"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/devops-ia/pr-generator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devops-ia%2Fpr-generator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devops-ia%2Fpr-generator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devops-ia%2Fpr-generator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devops-ia%2Fpr-generator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devops-ia","download_url":"https://codeload.github.com/devops-ia/pr-generator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devops-ia%2Fpr-generator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33415020,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"ssl_error","status_checked_at":"2026-05-23T22:14:43.778Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bitbucket","docker","github","helm","kubernetes"],"created_at":"2026-05-13T11:35:11.432Z","updated_at":"2026-05-23T23:01:56.577Z","avatar_url":"https://github.com/devops-ia.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PR generator\n\n[![CI](https://github.com/devops-ia/pr-generator/actions/workflows/docker-build.yml/badge.svg)](https://github.com/devops-ia/pr-generator/actions/workflows/docker-build.yml)\n[![GitHub release](https://img.shields.io/github/v/release/devops-ia/pr-generator)](https://github.com/devops-ia/pr-generator/releases)\n[![Docker Hub](https://img.shields.io/docker/v/devopsiaci/pr-generator?label=Docker%20Hub\u0026logo=docker)](https://hub.docker.com/r/devopsiaci/pr-generator)\n[![Docker Pulls](https://img.shields.io/docker/pulls/devopsiaci/pr-generator?logo=docker)](https://hub.docker.com/r/devopsiaci/pr-generator)\n[![Python](https://img.shields.io/badge/python-3.11%2B-blue?logo=python\u0026logoColor=white)](https://www.python.org)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nAutomated Pull Request creation daemon for **GitHub** and **Bitbucket Cloud**.\n\n`pr-generator` runs as a long-lived service that periodically scans your repository branches, matches them against configurable regex patterns, and automatically opens Pull Requests toward the configured destination branches — skipping any PR that already exists.\n\n---\n\n## Table of Contents\n\n- [How it works](#how-it-works)\n- [Quick start](#quick-start)\n- [Configuration](#configuration)\n  - [YAML file](#yaml-file)\n  - [Environment variables](#environment-variables)\n- [Providers](#providers)\n  - [GitHub — App authentication](#github--app-authentication)\n  - [GitHub — PAT authentication](#github--pat-authentication)\n  - [Bitbucket Cloud](#bitbucket-cloud)\n- [Rules](#rules)\n- [ArgoCD Image Updater integration](#argocd-image-updater-integration)\n- [Annotation-based discovery](#annotation-based-discovery)\n- [Health endpoints](#health-endpoints)\n- [Prometheus metrics](#prometheus-metrics)\n- [Docker](#docker)\n- [Development](#development)\n- [Troubleshooting](#troubleshooting)\n\n---\n\n## How it works\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                        Scan cycle                           │\n│                                                             │\n│  1. Fetch all branches   ──▶  GitHub  /  Bitbucket          │\n│  2. For every rule                                          │\n│       match branches against regex pattern                  │\n│       for each match                                        │\n│         skip  if open PR already exists                     │\n│         create PR  source ──▶ destination                   │\n│  3. Sleep scan_frequency seconds                            │\n│  4. Repeat                                                  │\n└─────────────────────────────────────────────────────────────┘\n```\n\nKey design points:\n\n- **Concurrent**: branches are fetched from all providers in parallel; rule×provider pairs are also processed concurrently (up to 10 workers).\n- **Idempotent**: an existing open PR for the same source→destination pair is detected and skipped.\n- **Dry-run mode**: log what would be created without actually calling the API.\n- **Graceful shutdown**: handles `SIGTERM` / `SIGINT` and drains in-progress work.\n\n---\n\n## Quick start\n\n```bash\n# Install\npip install -e .\n\n# Point to your config file and run\nCONFIG_PATH=./config.yaml pr-generator\n```\n\nOr with Docker:\n\n```bash\ndocker run --rm \\\n  -v \"$(pwd)/config.yaml:/etc/pr-generator/config.yaml:ro\" \\\n  ghcr.io/devops-ia/pr-generator:latest\n```\n\n---\n\n## Configuration\n\n### YAML file\n\nThe default config path is `/etc/pr-generator/config.yaml`. Override with the `CONFIG_PATH` environment variable. The application exits with an error at startup if the file is not found.\n\n```yaml\n# config.yaml\n\n# How often (seconds) to scan for new branches.\nscan_frequency: 300        # default: 300\n\n# Logging level: DEBUG | INFO | WARNING | ERROR\nlog_level: INFO            # default: INFO\n\n# Log format: \"text\" (human-readable) or \"json\" (structured, for log aggregators)\nlog_format: text           # default: text\n\n# When true, PRs are logged but never actually created.\ndry_run: false             # default: false\n\n# Port for the built-in health server.\nhealth_port: 8080          # default: 8080\n\nproviders:\n  github:\n    enabled: true\n    owner: my-org\n    repo: my-repo\n    app_id: \"123456\"\n    installation_id: \"78901234\"   # optional — auto-resolved if omitted\n    private_key_path: /secrets/github-app.pem   # path to PEM file\n    # Alternative: set GITHUB_APP_PRIVATE_KEY env var (plain PEM or base64-encoded)\n    timeout: 30            # HTTP timeout in seconds\n\n  bitbucket:\n    enabled: true\n    workspace: my-workspace\n    repo_slug: my-repo\n    token_env: BITBUCKET_TOKEN   # name of the env var that holds the token\n    close_source_branch: true    # delete source branch after merge (default: true)\n    timeout: 30\n\nrules:\n  - pattern: \"feature/.*\"          # Python regex matched against branch names\n    destinations:\n      github: main\n      bitbucket: develop\n\n  - pattern: \"release/.*\"\n    destinations:\n      github: main\n\n  - pattern: \".*-hotfix-.*\"\n    destinations:\n      bitbucket: master\n```\n\n#### Multiple GitHub organisations\n\nUse any name as the provider key and set `type: github` (or `type: bitbucket`) to identify the implementation. Rules reference providers by their name.\n\n```yaml\nproviders:\n  github-acme:\n    type: github          # required for non-standard key names\n    enabled: true\n    owner: acme-org\n    repo: backend\n    app_id: \"111\"\n    private_key_path: /secrets/acme-app.pem\n\n  github-skunkworks:\n    type: github\n    enabled: true\n    owner: skunkworks-org\n    repo: platform\n    auth_method: pat\n    token_env: SKUNKWORKS_GITHUB_TOKEN\n\n  bitbucket:              # \"github\" / \"bitbucket\" keys default type automatically\n    enabled: true\n    workspace: my-workspace\n    repo_slug: my-repo\n    token_env: BITBUCKET_TOKEN\n\nrules:\n  - pattern: \"feature/.*\"\n    destinations:\n      github-acme: main\n      github-skunkworks: develop\n      bitbucket: develop\n```\n\n**Config fields reference**\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `scan_frequency` | int | `300` | Seconds between scan cycles |\n| `log_level` | string | `\"INFO\"` | Python logging level |\n| `dry_run` | bool | `false` | Simulate PR creation without API calls |\n| `health_port` | int | `8080` | Port for health HTTP server |\n| `providers.\u003cname\u003e.type` | string | *(key name)* | Provider implementation: `github` or `bitbucket`. Required when the key name is not `github` or `bitbucket` |\n| `providers.\u003cname\u003e.enabled` | bool | `false` | Activate this provider instance. If no providers are enabled the application starts in **idle mode** — it logs a warning and keeps running without performing any scans |\n| `providers.\u003cname\u003e.owner` | string | — | GitHub organisation or user *(GitHub only)* |\n| `providers.\u003cname\u003e.repo` | string | — | Repository name *(GitHub only)* |\n| `providers.\u003cname\u003e.app_id` | string | — | GitHub App ID *(GitHub App auth)* |\n| `providers.\u003cname\u003e.installation_id` | string | *(auto)* | Installation ID; resolved automatically if omitted *(GitHub App auth)* |\n| `providers.\u003cname\u003e.private_key_path` | string | — | Path to GitHub App private key PEM file *(GitHub App auth)* |\n| `providers.\u003cname\u003e.auth_method` | string | `\"app\"` | `app` (GitHub App) or `pat` (Personal Access Token) *(GitHub only)* |\n| `providers.\u003cname\u003e.token_env` | string | `\"GITHUB_TOKEN\"` / `\"BITBUCKET_TOKEN\"` | Env var name containing the token *(PAT / Bitbucket)*. Must be **unique** across all enabled providers of the same type — duplicate values raise a `ValueError` at startup |\n| `providers.\u003cname\u003e.workspace` | string | — | Bitbucket workspace slug *(Bitbucket only)* |\n| `providers.\u003cname\u003e.repo_slug` | string | — | Bitbucket repository slug *(Bitbucket only)* |\n| `providers.\u003cname\u003e.close_source_branch` | bool | `true` | Delete source branch after PR merges *(Bitbucket only)* |\n| `providers.\u003cname\u003e.timeout` | float | `30` | HTTP timeout (seconds) |\n| `rules[].pattern` | string | — | Python regex applied to branch names |\n| `rules[].destinations` | map | — | `provider_name: destination_branch` pairs |\n\n---\n\n## Environment variables\n\n| Variable | Description |\n|----------|-------------|\n| `CONFIG_PATH` | Path to the YAML config file. Default: `/etc/pr-generator/config.yaml` |\n| `GITHUB_APP_PRIVATE_KEY` | GitHub App PEM key (plain text or base64-encoded). Used **only** when `private_key_path` is absent or empty in config — if `private_key_path` is set but the file does not exist, the application raises `FileNotFoundError` without falling back to this variable |\n| `GITHUB_TOKEN` | Default token env var for GitHub PAT providers (`token_env: GITHUB_TOKEN`) |\n| `BITBUCKET_TOKEN` | Default token env var for Bitbucket providers (`token_env: BITBUCKET_TOKEN`) |\n| *any name* | Custom env var referenced by `token_env` in provider config |\n\n---\n\n## Providers\n\n### GitHub App\n\nAuthentication uses a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). Two modes are available:\n\n**GitHub App (recommended)** — the provider:\n1. Signs a short-lived JWT with the App's RSA private key.\n2. Exchanges it for an installation access token (cached up to ~55 minutes).\n3. Uses the installation token for all API calls.\n4. Caches per-cycle PR-existence and branch-existence lookups to reduce API usage.\n\n**Personal Access Token (PAT)** — set `auth_method: pat` and point `token_env` at an env var holding the PAT.\n\nRequired GitHub App permissions: **Contents** (read), **Pull requests** (read \u0026 write).\n\n### Bitbucket Cloud\n\nAuthentication uses a project/repository **Bearer token** (HTTP access token).\n\nThe provider fetches default reviewers at PR creation time and automatically includes them in the payload.\n\nRequired Bitbucket permissions: **Repositories** (read), **Pull requests** (read \u0026 write).\n\n---\n\n## Rules\n\nEach rule has:\n\n- **`pattern`** — a Python regex (`re.compile`) matched against branch names using `re.match` (anchored at the start). The destination branch is excluded from matching.\n- **`destinations`** — a map of `provider_name → destination_branch`. Only providers that are both listed here **and** active in `providers` are processed.\n\n```yaml\nrules:\n  - pattern: \"feature/.*\"\n    destinations:\n      github: main          # create PRs toward \"main\" on GitHub\n      bitbucket: develop    # create PRs toward \"develop\" on Bitbucket\n```\n\nMultiple rules are supported.\n\n---\n\n## ArgoCD Image Updater integration\n\n`pr-generator` pairs naturally with [Argo CD Image Updater](https://argocd-image-updater.readthedocs.io/).\nImage Updater creates branches named `argocd-image-updater-set-\u003capp\u003e-\u003cenv\u003e-\u003cimage\u003e`.\nConfigure rules to catch those branches and open PRs toward the appropriate target branch per environment.\n\n```yaml\nscan_frequency: 120\n\nproviders:\n  github:\n    enabled: true\n    owner: my-org\n    repo: gitops-repo\n    auth_method: app\n    app_id: \"123456\"\n    private_key_path: /secrets/github-app.pem\n\nrules:\n  - pattern: \"argocd-image-updater-.*-dev-.*\"\n    destinations:\n      github: develop\n\n  - pattern: \"argocd-image-updater-.*-staging-.*\"\n    destinations:\n      github: staging\n\n  - pattern: \"argocd-image-updater-.*-pro-.*\"\n    destinations:\n      github: main\n```\n\n---\n\n## Annotation-based discovery\n\nInstead of a central `rules` list, each ArgoCD Application CR can carry annotations\nthat define its own PR rules. `pr-generator` reads these annotations on every scan cycle\n— no restart or config change required.\n\n### Modes\n\n| Mode | Behaviour |\n|------|-----------|\n| `config_only` | Static rules from `config.yaml` only. No Kubernetes API access. **Default.** |\n| `annotations_only` | Rules come exclusively from annotated ArgoCD Applications. `rules:` is ignored at runtime. |\n| `hybrid` | Both sources active. Annotation destinations win on same pattern+provider collision. |\n\n### Annotation schema\n\n```yaml\napiVersion: argoproj.io/v1alpha1\nkind: Application\nmetadata:\n  name: my-app\n  annotations:\n    pr-generator.io/enabled: \"true\"\n    pr-generator.io/pattern: \"^image-updater/.*\"\n    pr-generator.io/destination.github: \"main\"       # provider key → base branch\n    pr-generator.io/destination.bitbucket: \"develop\"\n```\n\n### config.yaml\n\n```yaml\nannotation_discovery:\n  mode: hybrid                    # config_only | annotations_only | hybrid\n  annotation_prefix: pr-generator.io   # default\n\n# rules: required when mode is config_only or hybrid; optional for annotations_only\nrules:\n  - pattern: \"^hotfix/.*\"\n    destinations:\n      github: main\n```\n\n### RBAC requirement\n\nAnnotation discovery reads `applications.argoproj.io` cluster-wide. The Helm chart\ncreates a `ClusterRole` and `ClusterRoleBinding` automatically when\n`annotationDiscovery.enabled: true`. For bare Docker/pip deployments, the pod's\nServiceAccount needs:\n\n```yaml\nrules:\n  - apiGroups: [\"argoproj.io\"]\n    resources: [\"applications\"]\n    verbs: [\"get\", \"list\"]\n```\n\n---\n\n## Health endpoints\n\nA lightweight HTTP server starts on `health_port` (default `8080`):\n\n| Endpoint | Behaviour |\n|----------|-----------|\n| `GET /livez` | `200 live` while running; `503 shutting down` during shutdown |\n| `GET /healthz` | Same as `/livez` (alias) |\n| `GET /readyz` | `200 ready` after the **first** scan cycle completes; `503 not ready` before that |\n| `GET /metrics` | Prometheus text exposition (see [Prometheus metrics](#prometheus-metrics)) |\n\nSuitable for Kubernetes liveness, readiness, and startup probes:\n\n```yaml\nlivenessProbe:\n  httpGet:\n    path: /livez\n    port: 8080\nreadinessProbe:\n  httpGet:\n    path: /readyz\n    port: 8080\n```\n\n---\n\n## Prometheus metrics\n\n`pr-generator` exposes Prometheus metrics at `GET /metrics` on the health port (default `8080`).\n\n### Metrics reference\n\n| Metric | Type | Labels | Description |\n|--------|------|--------|-------------|\n| `pr_generator_scan_cycles_total` | Counter | — | Scan cycles completed |\n| `pr_generator_scan_duration_seconds` | Histogram | — | Duration per cycle (buckets: .1, .5, 1, 5, 10, 30, 60 s) |\n| `pr_generator_last_scan_timestamp_seconds` | Gauge | — | Unix timestamp of last completed cycle |\n| `pr_generator_prs_created_total` | Counter | `provider` | PRs opened |\n| `pr_generator_prs_skipped_total` | Counter | `provider` | PRs skipped (already open) |\n| `pr_generator_prs_simulated_total` | Counter | `provider` | PRs simulated (`dry_run: true`) |\n| `pr_generator_scan_errors_total` | Counter | `provider` | Errors during branch fetch or PR creation |\n| `pr_generator_rules_active` | Gauge | — | Rules active in the current cycle |\n| `pr_generator_annotation_rules_discovered` | Gauge | — | Rules discovered from ArgoCD annotations in last cycle |\n\nThe `provider` label value is the key name from `config.providers` (e.g. `github`, `my-bitbucket`).\n\n### Scraping\n\n```bash\ncurl http://localhost:8080/metrics\n```\n\n### Helm chart — Prometheus Operator\n\n```yaml\nmetrics:\n  enabled: true\n  serviceMonitor:\n    enabled: true          # creates ServiceMonitor CRD\n    interval: 30s\n    labels:\n      release: kube-prometheus-stack   # match your Operator's serviceMonitorSelector\n```\n\n### Programmatic API\n\n```python\nfrom prometheus_client import CollectorRegistry\nfrom pr_generator.metrics import PrGeneratorMetrics\n\n# Isolated registry (useful in tests)\nm = PrGeneratorMetrics(registry=CollectorRegistry())\nm.record_annotation_rules(3)\nprint(m.generate_latest().decode())\n```\n\n---\n\n## Docker\n\nThe image is built from a two-stage Dockerfile:\n\n- **Stage 1** – installs Python dependencies into `/install`.\n- **Stage 2** – minimal `python:3.14-slim` runtime; runs as a non-root user (`prgen`).\n\n```bash\n# Build\ndocker build -t pr-generator .\n\n# Run with YAML config\ndocker run --rm \\\n  -v \"$(pwd)/config.yaml:/etc/pr-generator/config.yaml:ro\" \\\n  -v \"$(pwd)/github-app.pem:/secrets/github-app.pem:ro\" \\\n  -e BITBUCKET_TOKEN=\u003ctoken\u003e \\\n  -p 8080:8080 \\\n  pr-generator\n```\n\n---\n\n## Development\n\n**Prerequisites**: Python ≥ 3.11\n\n```bash\n# Create and activate a virtual environment\npython -m venv .venv\nsource .venv/bin/activate\n\n# Install the package in editable mode with dev extras\npip install -e .\npip install pytest\n\n# Run tests\npytest\n\n# Run with a local config\nCONFIG_PATH=./config.yaml python -m pr_generator\n```\n\n**Project layout**\n\n```\nsrc/pr_generator/\n├── __main__.py          # Entry point: startup, provider init, scan loop\n├── config.py            # Config loading from YAML file\n├── models.py            # Dataclasses: AppConfig, ProviderConfig, ScanRule, …\n├── scanner.py           # Concurrent scan cycle orchestrator\n├── health.py            # HTTP health server (/livez, /readyz, /healthz)\n├── http_client.py       # Shared HTTP client with retry/backoff\n├── annotation_discovery.py  # Kubernetes annotation-based rule discovery\n├── config.py            # Config loader (YAML → AppConfig)\n├── health.py            # HTTP health + metrics server (/livez, /readyz, /metrics)\n├── logging_config.py    # Logging setup (plain text or structured JSON)\n├── metrics.py           # Prometheus metrics (PrGeneratorMetrics)\n└── providers/\n    ├── base.py          # ProviderInterface Protocol\n    ├── github.py        # GitHub App provider\n    └── bitbucket.py     # Bitbucket Cloud provider\n\ntests/\n├── conftest.py                  # Shared pytest fixtures\n├── test_annotation_discovery.py # Annotation discovery tests\n├── test_config.py               # Config loading tests\n├── test_health.py               # Health server tests\n├── test_metrics.py              # Prometheus metrics tests\n├── test_models.py               # Model tests\n└── test_scanner.py              # Scan cycle tests\n```\n\n---\n\n## Troubleshooting\n\n### Application exits with `FileNotFoundError`\n\n```\nFileNotFoundError: [Core] private_key_path '/secrets/github-app.pem' does not exist.\n```\n\n`private_key_path` is set in `config.yaml` but the file is not present at that path.\nEither mount the PEM file at the configured path, or remove `private_key_path` from\nthe config and set the `GITHUB_APP_PRIVATE_KEY` environment variable instead.\n\n### `ValueError: duplicate tokenEnv`\n\n```\nValueError: [Core] Providers 'bb-eu' and 'bb-us' both use tokenEnv 'BITBUCKET_TOKEN'.\n```\n\nTwo enabled providers of the same type share the same `token_env` value. Assign a\nunique env var name to each provider and export the corresponding variable in your\nruntime environment.\n\n### `/readyz` returns `503`\n\nThis is expected during startup. The endpoint returns `503 not ready` until the first\nfull scan cycle completes. If it never flips to `200`, check the application logs for\nerrors in the scan cycle (API auth failures, missing config fields, network issues).\n\n### No PRs are created (dry_run is false, branches exist)\n\n1. **Regex anchoring** — rules use `re.match`, which is anchored at the start of the\n   string. A pattern `feature/.*` will **not** match `hotfix/feature/x`. Enable\n   `log_level: DEBUG` to see per-branch matching decisions.\n2. **Provider name mismatch** — the name in `rules[].destinations` must exactly match\n   the provider key under `providers:`.\n3. **Destination branch excluded** — pr-generator skips branches whose name equals the\n   destination branch to avoid self-targeting PRs.\n\n### GitHub App: `RuntimeError: Could not resolve installation id`\n\nSet `installation_id` explicitly in the provider config (find it in your GitHub App\nsettings under _Installations_), or ensure the GitHub App is installed on the target\nrepository.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevops-ia%2Fpr-generator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevops-ia%2Fpr-generator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevops-ia%2Fpr-generator/lists"}