> ## Documentation Index
> Fetch the complete documentation index at: https://otel.fyi/llms.txt
> Use this file to discover all available pages before exploring further.

# Github

> OpenTelemetry receiver for Github

# Github Receiver

![Status](https://img.shields.io/badge/status-alpha-red)

**Available in:** `contrib`

**Maintainers:** [@adrielp](https://github.com/adrielp), [@crobert-1](https://github.com/crobert-1), [@TylerHelmuth](https://github.com/TylerHelmuth)

**Source:** [opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/githubreceiver)

## Supported Telemetry

![Metrics](https://img.shields.io/badge/metrics-alpha-green) ![Traces](https://img.shields.io/badge/traces-alpha-orange)

## Overview

* [Overview](#overview)
* [Metrics - Getting Started](#metrics---getting-started)
  * [Scraping](#scraping)
* [Traces - Getting Started](#traces---getting-started)
  * [Receiver Configuration](#receiver-configuration)
  * [Configuring Service Name](#configuring-service-name)
  * [Configuration a GitHub App](#configuration-a-github-app)
  * [Custom Properties as Resource Attributes](#custom-properties-as-resource-attributes)

## Overview

The GitHub receiver receives data from [GitHub](https://github.com) via two methods:

1. Scrapes [version control system][vcsm] metrics from GitHub repositories and
   organizations using the GraphQL and REST APIs.
2. Receives GitHub Actions events by serving a webhook endpoint, converting
   those events into traces.

## Metrics - Getting Started

The current default set of metrics can be found in
[documentation.md](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/githubreceiver/documentation.md).

These metrics can be used as leading indicators ([capabilities][doracap])
to the [DORA][dorafour] metrics; helping provide insight into modern-day
engineering practices.

The collection interval is common to all scrapers and is set to 30 seconds by default.

> Note: Generally speaking, if the vendor allows for anonymous API calls, then you
> won't have to configure any authentication, but you may only see public repositories
> and organizations. You may also run into significantly more rate limiting.

```yaml theme={null}
github:
    collection_interval: <duration> #default = 30s recommended 300s
    scrapers:
        scraper/config-1:
        scraper/config-2:
        ...
```

A more complete example using the GitHub scrapers with authentication is as follows:

```yaml theme={null}
extensions:
    bearertokenauth/github:
        token: ${env:GH_PAT}

receivers:
    github:
        initial_delay: 1s
        collection_interval: 60s
        scrapers:
            scraper:
                metrics: #Optional
                    vcs.contributor.count:
                        enabled: true
                github_org: <myfancyorg> 
                search_query: "org:<myfancyorg> topic:<o11yalltheway>" # Recommended optional query override, defaults to "{org,user}:<github_org>"
                concurrency_limit: 50  # Optional: (default: 50)
                merged_pr_lookback_days: 30 # Optional: (default: 30)
                endpoint: "https://selfmanagedenterpriseserver.com" # Optional
                auth:
                    authenticator: bearertokenauth/github
service:
    extensions: [bearertokenauth/github]
    pipelines:
        metrics:
            receivers: [..., github]
            processors: []
            exporters: [...]
```

### Configuration

`github_org` (**required**): Specify the GitHub organization or username to scrape.

`endpoint` (optional): Set this only when using a self-managed GitHub instance (e.g., `https://selfmanagedenterpriseserver.com` -- SHOULD NOT include `api` subdomain or `/graphql` context path).

`search_query` (optional): A filter to narrow down repositories. Defaults to `org:<github_org>` (or `user:<username>`). For example, use `repo:<org>/<repo>` to target a specific repository. Any valid GitHub search syntax is allowed.

`concurrency_limit` (optional): Maximum number of concurrent repository processing goroutines. Defaults to `50` to stay under GitHub's 100 concurrent request limit. Set to `0` for unlimited concurrency (not recommended for >100 repositories).

`merged_pr_lookback_days` (optional):  Number of days to query back in time when fetching merged pull requests. Defaults to 30. Set to `0` to fetch all merged PRs.

`metrics` (optional): Enable or disable metrics scraping. See the [metrics documentation](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/githubreceiver/documentation.md) for details.

### Scraping

> Important:
>
> * The GitHub scraper does not emit metrics for branches that have not had
>   changes since creation from the default branch (trunk).
> * Due to GitHub API limitations, it is possible for the branch time metric to
>   change when rebases occur, recreating the commits with new timestamps.

For additional context on GitHub scraper limitations and inner workings please
see the [Scraping README][ghsread].

[ghsread]: internal/scraper/githubscraper/README.md#github-limitations

### GitHub Personal Access Token (PAT) Setup

To create a GitHub Personal Access Token (PAT), please refer to the official [documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).

**Organization or Personal Access:**
When generating the PAT, select the appropriate `Resource owner` — either your personal account or the organization and choose the correct `Repository access` type. For fine-grained tokens, explicitly configure the necessary `Repository permissions` or `Organization permissions`.

**Note**:
The PAT must have read access to the target repositories. If the PAT doesn't have permission to access repositories in the target organization, only the repository count metric will be available. Detailed repository metrics cannot be fetched.

## Traces - Getting Started

Workflow tracing support is accomplished through the processing of GitHub
Actions webhook events for workflows and jobs. The [`workflow_job`][wjob] and
[`workflow_run`][wrun] event payloads are then constructed into `trace`
telemetry.

Each GitHub Action workflow or job, along with its steps, are converted
into trace spans, allowing the observation of workflow execution times,
success, and failure rates. Each Trace and Span ID is deterministic. This
enables the underlying actions to emit telemetry from any command running in any
step. This can be achieved by using tools like the [run-with-telemetry
action][run] and [otel-cli][otcli]. The key is generating IDs in the same way
that this GitHub receiver does. The [trace\_event\_handling.go][tr] file contains
the `new*ID` functions that generate deterministic IDs.

**Trace ID and root span ID** are derived from `${{ github.run_id }}` and
`${{ github.run_attempt }}` — both available in any runner context.

**Job, step, and queue span IDs** are derived from the job's `check_run_id`,
available in-runner as `${{ job.check_run_id }}`. Step span IDs additionally
incorporate the step's raw `name:` field, making them fully reproducible from
values available inside a step.
The receiver uses `check_run_id` when the
`receiver.githubreceiver.UseCheckRunID` feature gate is enabled (default: on).
Disable with `--feature-gates=-receiver.githubreceiver.UseCheckRunID` to retain
the legacy scheme.

| Span  | Hash input                                      |
| ----- | ----------------------------------------------- |
| Trace | `sha256("{run_id}{run_attempt}t")[0:32]`        |
| Root  | `sha256("{run_id}{run_attempt}s")[16:32]`       |
| Job   | `sha256("{check_run_id}-j")[16:32]`             |
| Step  | `sha256("{check_run_id}-{step_name}-s")[16:32]` |
| Queue | `sha256("{check_run_id}-q")[16:32]`             |

`{step_name}` is the step's raw `name:` as written in the workflow YAML. This
avoids any dependency on the runner-assigned `step.number`, which is not
exposed in the GitHub Actions execution context.

**IMPORTANT** - Step names MUST be unique within a job for deterministic step
span IDs to not collide. Neither GitHub Actions nor `actionlint` enforce this,
but the receiver logs a WARN when duplicates are detected in a payload, and
duplicate steps will share a span ID (child spans attach to the first
occurrence). Prefer unique, stable `name:` values for any step that needs to
emit correlated telemetry.

To generate a matching `TRACEPARENT` inside a step with only built-in Actions
context (no REST API call, no step number), use the following shell helper:

```bash theme={null}
# With receiver.githubreceiver.UseCheckRunID enabled (default from v0.151.0).
# Usage inside a step (requires unique step names within the job):
#
#   - name: &step_name "My Step"
#     env:
#       STEP_NAME: *step_name
#     run: |
#       export TRACEPARENT=$(otel_github_traceparent "$STEP_NAME")
otel_github_traceparent() {
  trace_id=$(printf '%s' "${GITHUB_RUN_ID}${GITHUB_RUN_ATTEMPT}t" | sha256sum | cut -c1-32)
  span_id=$(printf '%s' "${{ job.check_run_id }}-$1-s"            | sha256sum | cut -c17-32)
  printf '00-%s-%s-01\n' "$trace_id" "$span_id"
}
```

The YAML anchor pattern (`&step_name` / `*step_name`) avoids duplicating the
step name between the `name:` field and the `env:` block.

**Legacy scheme** (gate disabled): job, step, and queue IDs are hashed from
`{run_id}{run_attempt}{job_name}{…}`. Step IDs under the legacy scheme
additionally incorporate the runner-assigned `step.number`.

**IMPORTANT** - When using the legacy scheme (gate disabled), workflow job
names MUST be unique in each workflow for deterministic span IDs to not
conflict with each other. GitHub does not enforce this behavior, but when
linting a workflow, warns that there are duplicate job names. The
`UseCheckRunID` gate (default) removes this constraint for job-level
uniqueness because `check_run_id` is globally unique per job attempt, but
introduces the step-name uniqueness constraint described above.

### Receiver Configuration

**IMPORTANT** - Ensure your WebHook endpoint is secured with a secret and a Web
Application Firewall (WAF) or other security measure.

The WebHook configuration exposes the following settings:

* `endpoint`: (default = `localhost:8080`) - The address and port to bind the WebHook to.
* `path`: (default = `/events`) - The path for Action events to be sent to.
* `health_path`: (default = `/health`) - The path for health checks.
* `secret`: (optional) - The secret used to [validates the payload][valid].
* `required_headers`: (optional) - One or more header key/value pairs that every webhook request must carry (the health check endpoint is exempt). Requests missing any configured header, or carrying the wrong value, are rejected before the payload is parsed. GitHub itself does not send custom request headers, so this setting is intended for deployments where a front-end WAF or proxy injects the agreed header.
* `service_name`: (optional) - The service name for the traces. See the
  [Configuring Service Name](#configuring-service-name) section for more
  information.
* `include_span_events`: (default = `false`) - When set to `true`, attaches the raw webhook event JSON as a span event. The workflow run event is attached to the workflow run span, and the workflow job event is attached to the job span.

The WebHook configuration block also accepts all the [confighttp][cfghttp]
settings.

An example configuration is as follows:

```yaml theme={null}
receivers:
    github:
        webhook:
            endpoint: localhost:19418
            path: /events
            health_path: /health
            secret: ${env:SECRET_STRING_VAR}
            service_name: github-actions  # single logical CI service (See Configuring Service Name section below)
            required_headers:
                WAF-Header: "value"
        scrapers: # The validation expects at least a dummy scraper config
            scraper:
                github_org: open-telemetry
```

For tracing, all configuration is set under the `webhook` key. The full set
of exposed configuration values can be found in [`config.go`](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/githubreceiver/config.go).

### Configuring Service Name

The `service_name` option in the WebHook configuration can be used to set a
pre-defined `service.name` resource attribute for all traces emitted by the
receiver. This value should represent the **logical service producing telemetry**
(as defined by OpenTelemetry resource semantics), not individual repositories or
components. For CI/CD usage, a typical choice is a single service such as
`github-actions` (optionally paired with `service.namespace` for ownership and
uniqueness).

If you choose to set `service_name` explicitly, consider running a separate
GitHub receiver (and/or GitHub App) for each distinct service that you want to
model.

If you do not set `service_name`, the receiver supports deriving it from
repository metadata. You can configure [Custom Properties][cp] in each GitHub
repository by adding a `service_name` key; all events from that repository will
then carry the specified `service.name`. If no custom property is found, the
receiver will derive `service.name` from the repository name.

> **Note:** Deriving `service.name` from repositories is a convenience and may
> be sufficient for small setups. In larger organizations, mapping repositories
> directly to `service.name` often leads to many pseudo-services and can make
> cross-repository analysis harder. Prefer an explicit CI service name when
> modeling your pipelines as a platform service.

**Precedence for setting `service.name`:**

1. `service_name` in the WebHook configuration.
2. `service_name` key in the repository’s Custom Properties.
3. `service_name` derived from the repository name.
4. `service.name` defaults to `unknown_service` per the semantic conventions.

### Span Events

When `include_span_events` is enabled, the receiver attaches the raw GitHub webhook event JSON as a span event to the corresponding span:

* **Workflow Run events**: Attached as a span event named `github.workflow_run.event` to the root workflow run span
* **Workflow Job events**: Attached as a span event named `github.workflow_job.event` to the job span

The raw event is stored in the `event.payload` attribute as a JSON string. This allows for detailed inspection of the complete webhook payload, including fields that may not be mapped to span attributes.

**Note**: The raw event payload can be large (typically 5-50KB). Consider the impact on storage and performance before enabling this feature in production environments.

An example configuration with span events enabled:

```yaml theme={null}
receivers:
    github:
        webhook:
            endpoint: localhost:19418
            path: /events
            health_path: /health
            secret: ${env:SECRET_STRING_VAR}
            required_headers:
                WAF-Header: "value"
            include_span_events: true
        scrapers: # The validation expects at least a dummy scraper config
            scraper:
                github_org: open-telemetry
```

### Configuring A GitHub App

To configure a GitHub App, you will need to create a new GitHub App within your
organization. Refer to the general [GitHub App documentation][ghapp] for how to
create a GitHub App. During the subscription phase, subscribe to `workflow_run` and `workflow_job` events.

[wjob]: https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_job

[wrun]: https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_run

[valid]: https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries

[cfghttp]: https://pkg.go.dev/go.opentelemetry.io/collector/config/confighttp#ServerConfig

[cp]: https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization

[vcsm]: https://opentelemetry.io/docs/specs/semconv/cicd/cicd-metrics/#vcs-metrics

[doracap]: https://dora.dev/capabilities/

[dorafour]: https://dora.dev/guides/dora-metrics-four-keys/

[ghapp]: https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app

[run]: https://github.com/krzko/run-with-telemetry

[otcli]: https://github.com/equinix-labs/otel-cli

[tr]: ./trace_event_handling.go

### Custom Properties as Resource Attributes

The GitHub receiver supports adding custom properties from GitHub repositories as resource attributes in your telemetry data. This allows users to enrich traces and events with additional metadata specific to each repository.

#### How It Works

When a GitHub webhook event is received, the receiver extracts all custom properties from the repository and adds them as resource attributes with the prefix `github.repository.custom_properties`.

For example, if your repository has these custom properties:

```
classification: public
service-tier: critical
slack-support-channel: #observability-alerts
team-name: observability-engineering
```

They will be added as resource attributes:

```
github.repository.custom_properties.classification: "public"
github.repository.custom_properties.service_tier: "critical"
github.repository.custom_properties.slack_support_channel: "#observability-alerts"
github.repository.custom_properties.team_name: "observability-engineering"
```

#### Key Formatting

To ensure consistency with OpenTelemetry naming conventions, all custom property keys are converted to snake\_case format using the following rules:

1. Hyphens, spaces, and dots are replaced with underscores
2. Special characters like `$` and `#` are replaced with `_dollar_` and `_hash_`
3. CamelCase and PascalCase are converted to snake\_case by inserting underscores before uppercase letters
4. Multiple consecutive underscores are replaced with a single underscore

Examples of key transformations:

| Original Key    | Transformed Key  |
| --------------- | ---------------- |
| `teamName`      | `team_name`      |
| `API-Key`       | `api_key`        |
| `Service.Level` | `service_level`  |
| `$Cost`         | `_dollar_cost`   |
| `#Priority`     | `_hash_priority` |

**Note**:
The `service_name` custom property is handled specially and is not added as a resource attribute with the prefix. Instead, it's used to set the `service.name` resource attribute directly, as described in the [Configuring Service Name](#configuring-service-name) section.

## Migration Notes

### Semantic Conventions v1.37.0 Upgrade

The GitHub receiver has been updated to use OpenTelemetry semantic conventions v1.37.0. This brings standardization improvements and better alignment with the broader OpenTelemetry ecosystem.

#### Breaking Changes

**Resource Attributes:**

* `organization.name` → `vcs.owner.name` - The resource attribute for organization/owner name has been standardized
* `vcs.vendor.name` → `vcs.provider.name` - The VCS provider attribute has been standardized

**Trace Attributes:**

* `vcs.ref.head.type` → `vcs.ref.type` - Some trace attributes now use standardized naming

#### What This Means for Users

**For Dashboard and Alerting Users:**

* Update your queries and dashboards to use the new attribute names
* Old attribute names are no longer emitted starting with this version
* The schema URL in telemetry data now references OpenTelemetry schemas v1.37.0

**For Configuration Users:**

* No configuration changes are required
* All existing receiver configurations continue to work unchanged

**Migration Timeline:**

* **Before upgrading:** Update downstream systems (dashboards, alerts, queries) to use new attribute names
* **After upgrading:** Verify that telemetry data is flowing correctly with the new attributes

For the complete list of semantic convention changes, see the [OpenTelemetry semantic conventions v1.37.0 documentation](https://opentelemetry.io/docs/specs/semconv/).

## Metrics

| Metric Name                     | Description                                                                                                                    | Unit           | Type  | Attributes                                                                                                                                              |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ✅ `vcs.change.count`            | The number of changes (pull requests) in a repository, categorized by their state (either open or merged).                     | \{change}      | Gauge | vcs.repository.url.full, vcs.change.state, vcs.repository.name                                                                                          |
| ✅ `vcs.change.duration`         | The time duration a change (pull request/merge request/changelist) has been in an open state.                                  | s              | Gauge | vcs.repository.url.full, vcs.repository.name, vcs.ref.head.name, vcs.change.state                                                                       |
| ✅ `vcs.change.time_to_approval` | The amount of time it took a change (pull request) to go from open to approved.                                                | s              | Gauge | vcs.repository.url.full, vcs.repository.name, vcs.ref.head.name                                                                                         |
| ✅ `vcs.change.time_to_merge`    | The amount of time it took a change (pull request) to go from open to merged.                                                  | s              | Gauge | vcs.repository.url.full, vcs.repository.name, vcs.ref.head.name                                                                                         |
| ❌ `vcs.contributor.count`       | The number of unique contributors to a repository.                                                                             | \{contributor} | Gauge | vcs.repository.url.full, vcs.repository.name                                                                                                            |
| ✅ `vcs.ref.count`               | The number of refs of type branch in a repository.                                                                             | \{ref}         | Gauge | vcs.repository.url.full, vcs.repository.name, vcs.ref.type                                                                                              |
| ✅ `vcs.ref.lines_delta`         | The number of lines added/removed in a ref (branch) relative to the default branch (trunk).                                    | \{line}        | Gauge | vcs.repository.url.full, vcs.repository.name, vcs.ref.head.name, vcs.ref.head.type, vcs.ref.base.name, vcs.ref.base.type, vcs.line\_change.type         |
| ✅ `vcs.ref.revisions_delta`     | The number of revisions (commits) a ref (branch) is ahead/behind the branch from trunk (default).                              | \{revision}    | Gauge | vcs.repository.url.full, vcs.repository.name, vcs.ref.head.name, vcs.ref.head.type, vcs.ref.base.name, vcs.ref.base.type, vcs.revision\_delta.direction |
| ✅ `vcs.ref.time`                | Time a ref (branch) created from the default branch (trunk) has existed. The `vcs.ref.type` attribute will always be `branch`. | s              | Gauge | vcs.repository.url.full, vcs.repository.name, vcs.ref.head.name, vcs.ref.head.type                                                                      |
| ✅ `vcs.repository.count`        | The number of repositories in an organization.                                                                                 | \{repository}  | Gauge |                                                                                                                                                         |

## Attributes

| Attribute Name                 | Description                                                               | Type   | Values             |
| ------------------------------ | ------------------------------------------------------------------------- | ------ | ------------------ |
| `vcs.change.state`             | The state of a change (pull request)                                      | string | `open`, `merged`   |
| `vcs.line_change.type`         | The type of line change being measured on a ref (branch).                 | string | `added`, `removed` |
| `vcs.ref.base.name`            | The name of the VCS base reference (branch/tag) for comparison.           | string |                    |
| `vcs.ref.base.type`            | The type of the base reference (branch, tag).                             | string | `branch`, `tag`    |
| `vcs.ref.head.name`            | The name of the VCS head reference (branch).                              | string |                    |
| `vcs.ref.head.type`            | The type of the head reference (branch, tag).                             | string | `branch`, `tag`    |
| `vcs.ref.type`                 | The type of the reference in the repository.                              | string | `branch`, `tag`    |
| `vcs.repository.name`          | The name of the VCS repository.                                           | string |                    |
| `vcs.repository.url.full`      | The canonical URL of the repository providing the complete HTTPS address. | string |                    |
| `vcs.revision_delta.direction` | The type of revision comparison.                                          | string | `ahead`, `behind`  |

## Resource Attributes

| Attribute Name      | Description                                       | Type   | Enabled |
| ------------------- | ------------------------------------------------- | ------ | ------- |
| `vcs.owner.name`    | The group owner within the version control system | string | ✅       |
| `vcs.provider.name` | The name of the version control system provider   | string | ✅       |

## Configuration

### Example Configuration

```yaml theme={null}
receivers:
  github:
    initial_delay: 1s
    collection_interval: 60s
    scrapers:
      scraper:
    webhook:
      endpoint: localhost:8080
      read_timeout: "500ms"
      write_timeout: "500ms"
      path: "some/path"
      health_path: "health/path"
      required_headers:
        key: value-present

  github/customname:
    initial_delay: 1s
    collection_interval: 30s
    scrapers:
      scraper:
    webhook:
      endpoint: localhost:8080
      read_timeout: "500ms"
      write_timeout: "500ms"
      path: "some/path"
      health_path: "health/path"
      required_headers:
        key: value-present

processors:
  nop:

exporters:
  nop:

service:
  pipelines:
    metrics:
      receivers: [github, github/customname]
      processors: [nop]
      exporters: [nop]
    traces:
      receivers: [github, github/customname]
      processors: [nop]
      exporters: [nop]
```

***

*Last generated: 2026-07-06*
