Export GitHub Actions workflow runs as OpenTelemetry traces and logs to groundcover.
- GitHub Actions workflow with
actions: readandissues: writepermissions - Node 24 runtime (requires GitHub Actions runner v2.327.1+; self-hosted runners must be updated separately)
- groundcover OTLP endpoint and ingestion key
name: Export CI Traces
on:
workflow_run:
workflows: ["CI"]
types: [completed]
jobs:
export-traces:
runs-on: ubuntu-latest
permissions:
actions: read
issues: write
steps:
- uses: groundcover-com/groundcover-github-action@v3
with:
groundcoverEndpoint: ${{ secrets.GC_ENDPOINT }}
apiKey: ${{ secrets.GC_API_KEY }}For groundcover setup details, see:
Use the Quick Start example above, then configure these two secrets in your repository or organization:
GC_ENDPOINTGC_API_KEY
Minimal groundcover setup:
- uses: groundcover-com/groundcover-github-action@v3
with:
groundcoverEndpoint: ${{ secrets.GC_ENDPOINT }}
apiKey: ${{ secrets.GC_API_KEY }}Required permissions:
permissions:
actions: read
issues: writeIf you are using Claude, Cursor, GitHub Copilot, or another coding agent, start with the repo-local instructions in llms.txt.
Use these rules when generating workflows or modifying this repository:
- Treat
action.ymlas the canonical input/output contract. - Use
apiKeywith your groundcover ingestion key and a workspace-specificgroundcoverEndpoint. - Preserve
traceparentwhen linking CI/CD and application traces. - Keep
source=github-actions, configurableworkload, and optionalenvresource attributes. - If you change this repository, run
npm run alland rebuilddist/before committing.
- Exports workflow runs, jobs, and steps as a nested OTEL span hierarchy
- Links CI/CD traces to application traces via W3C
traceparent - Supports OTLP/HTTP and OTLP/gRPC transports
- Follows the OTEL CI/CD semantic conventions
- Adds resource attributes for
source,workload, and optionalenv - Can parse JUnit XML test results and attach a summary to the workflow root span
- Supports additional custom resource attributes for team/region/metadata
- Upserts a single PR comment with trace details and a link to groundcover traces
Opening or updating a pull request triggers CI, and the Self Test workflow exports the completed CI run.
Using workflow_run is the recommended approach. It runs after your CI completes, so it doesn't add latency to your pipeline and always captures the full run including the final job status.
name: Export CI Traces
on:
workflow_run:
workflows: ["CI"]
types: [completed]
jobs:
export-traces:
runs-on: ubuntu-latest
permissions:
actions: read
issues: write
steps:
- uses: groundcover-com/groundcover-github-action@v3
with:
groundcoverEndpoint: ${{ secrets.GC_ENDPOINT }}
apiKey: ${{ secrets.GC_API_KEY }}
runId: ${{ github.event.workflow_run.id }}You can also add the export step directly to your existing workflow. Use if: always() so it runs even when earlier jobs fail.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
export-traces:
runs-on: ubuntu-latest
needs: [build]
if: always()
permissions:
actions: read
issues: write
steps:
- uses: groundcover-com/groundcover-github-action@v3
with:
groundcoverEndpoint: ${{ secrets.GC_ENDPOINT }}
apiKey: ${{ secrets.GC_API_KEY }}This pattern connects your CI/CD traces to the application traces produced by your deployment. The action uses a traceparent created during the build or deploy flow, passes it into your application, and forwards it to the export action. This creates a single trace spanning both CI and production.
This works best in the same workflow, but it can also work with a separate export workflow if the original workflow persists the traceparent somewhere the export workflow can read it back from, such as an artifact or deployment metadata.
name: CI + Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
outputs:
traceparent: ${{ steps.traceparent.outputs.traceparent }}
steps:
- uses: actions/checkout@v4
# Generate a traceparent to link CI and app traces
- name: Generate traceparent
id: traceparent
run: |
TRACE_ID=$(openssl rand -hex 16)
SPAN_ID=$(openssl rand -hex 8)
echo "traceparent=00-${TRACE_ID}-${SPAN_ID}-01" >> "$GITHUB_OUTPUT"
- run: npm ci
- run: npm test
- name: Deploy
env:
TRACEPARENT: ${{ steps.traceparent.outputs.traceparent }}
run: ./deploy.sh # your app picks up TRACEPARENT from the environment
export-traces:
runs-on: ubuntu-latest
needs: [build]
if: always()
permissions:
actions: read
issues: write
steps:
- uses: groundcover-com/groundcover-github-action@v3
with:
groundcoverEndpoint: ${{ secrets.GC_ENDPOINT }}
apiKey: ${{ secrets.GC_API_KEY }}
traceparent: ${{ needs.build.outputs.traceparent }}- uses: groundcover-com/groundcover-github-action@v3
with:
groundcoverEndpoint: ${{ secrets.GC_ENDPOINT }}
apiKey: ${{ secrets.GC_API_KEY }}
otelServiceName: my-service
env: production
workload: payments-api
testResultsGlob: "reports/junit/**/*.xml"
extraAttributes: "team=platform"The action always adds source=github-actions as a resource attribute.
Use your workspace-specific managed OTLP endpoint rather than a hardcoded shared URL. groundcover documents the endpoint format and OpenTelemetry setup here:
| Input | Required | Default | Description |
|---|---|---|---|
groundcoverEndpoint |
Yes | OTLP endpoint base URL. Supports https://, http://, and grpc:// schemes. Do not include /v1/traces — the exporter appends it automatically. |
|
apiKey |
Yes | groundcover ingestion key. | |
otlpHeaders |
No | Comma-separated key=value pairs sent as OTLP exporter headers. Advanced — takes precedence over apiKey when both are set. |
|
githubToken |
No | ${{ github.token }} |
GitHub token with actions:read permission. Required for private repos. Use secrets.GITHUB_TOKEN or a PAT. |
runId |
No | Current run | Workflow Run ID to export. Defaults to the current workflow run. When using workflow_run, set this to ${{ github.event.workflow_run.id }} to export the triggering run. |
otelServiceName |
No | Workflow name | Overrides the service.name OTEL resource attribute. Defaults to the workflow name. |
traceparent |
No | W3C Trace Context traceparent value (e.g., 00-<trace_id>-<span_id>-01). When provided, the workflow root span becomes a child of this trace, enabling correlation between CI/CD and application traces. |
|
env |
No | Environment name added to resource attributes (e.g., production, staging). |
|
workload |
No | Workflow name | Workload name added to resource attributes. Use this to group traces by service/workload. |
testResultsGlob |
No | Comma-separated glob patterns for JUnit XML test result files. Matching files are parsed and summarized onto the workflow root span. | |
extraAttributes |
No | Extra resource attributes as comma-separated key=value pairs. Example: "team=platform,region=us-east-1". Prefer using dedicated env and workload inputs when applicable. |
|
groundcoverBaseUrl |
No | https://app.groundcover.com |
Base URL used for the PR comment link to the groundcover Traces page. Use your workspace URL for self-hosted or custom domains. |
commentOnPr |
No | true |
Upserts a single PR comment with trace details and a Traces link pre-filtered by PR number. Requires issues: write permission. Set to false to disable. |
groundcoverDuration |
No | Last 6 hours |
Duration query parameter used in the PR comment traces link. |
groundcoverBackendId |
No | Optional backendId query parameter for the PR comment traces link. |
|
groundcoverTenantUUID |
No | Optional tenantUUID query parameter for the PR comment traces link. |
| Output | Description |
|---|---|
traceId |
The OpenTelemetry Trace ID of the exported trace. Use this to link to the trace in your observability backend. |
Required:
permissions:
actions: read
issues: write # required for PR trace comments (enabled by default)
pull-requests: write # required for PR trace comments via workflow_run triggersOptional:
permissions:
actions: read
issues: write
pull-requests: write
contents: read # required for private repositories
checks: read # enables exporting check annotationsFor private repositories, the default GITHUB_TOKEN may not have sufficient permissions to read workflow run data. You have two options:
Option 1: Grant contents: read in your workflow permissions block (recommended):
permissions:
actions: read
contents: readOption 2: Use a Personal Access Token with repo scope:
- uses: groundcover-com/groundcover-github-action@v3
with:
groundcoverEndpoint: ${{ secrets.GC_ENDPOINT }}
apiKey: ${{ secrets.GC_API_KEY }}
githubToken: ${{ secrets.MY_PAT }}Each workflow run is exported as a tree of spans:
workflow_run (root span)
job: build
step: Checkout
step: npm ci
step: npm test
job: lint
step: Checkout
step: Run linter
job: export-traces
step: groundcover OTEL CI/CD Export
Span attributes follow the OTEL CI/CD semantic conventions, including cicd.pipeline.name, cicd.pipeline.run.id, cicd.pipeline.task.name, cicd.pipeline.task.run.id, and cicd.pipeline.task.run.url.full.
By default, the action sets:
service.name(workflow name unless overridden viaotelServiceName)service.namespace(GitHubowner/repo)service.version(workflow head SHA)service.instance.id(owner/repo/workflow_id/run_id/run_attempt)source=github-actionsworkload(from input, defaults to workflow name)env(only when input is provided)
For groundcover users, source, workload, and env make it easier to filter and group CI/CD traces consistently with the rest of your telemetry.
If your workflow produces JUnit XML reports, set testResultsGlob to one or more comma-separated glob patterns. The action parses matching files and adds these workflow root span attributes:
test.suitestest.totaltest.passedtest.failedtest.skippedtest.errorstest.duration
The matching XML files must exist on disk in the job running this action. In a separate workflow_run export workflow, download the test result artifacts first if you want them included.
When you provide a traceparent input, the workflow root span is created as a child of that trace context. This means:
- Your build job (or deploy logic) generates a
traceparent(a trace ID + span ID pair). - You pass that
traceparentto your application at deploy time (e.g., as an environment variable). - Your application starts its own spans as children of that context.
- You pass the same
traceparentto this action. - The action creates the CI/CD trace as a child of the same root.
The result is a single trace in your observability backend that spans from the first CI step through to production request handling.
The action exports the wrong workflow run.
When using workflow_run, the action defaults to the current run (the export workflow itself). Set runId: ${{ github.event.workflow_run.id }} to export the triggering workflow instead.
I'm getting 401 or 403 errors from the OTLP endpoint.
Check that your apiKey secret contains the correct groundcover ingestion key. If using otlpHeaders for a custom setup, verify the header name and value match what your backend expects.
Jobs or steps are missing from the trace.
The action reads job and step data from the GitHub API. For private repositories, ensure contents: read is included in your permissions. If steps are still missing, the GitHub API may not have finished indexing the run data; adding a short sleep before the export step can help.
The action fails with "Resource not accessible by integration".
Your token doesn't have actions: read. Add it to your workflow's permissions block.
gRPC connections are timing out.
Ensure your groundcoverEndpoint uses the grpc:// scheme and that port 443 is reachable from GitHub Actions runners. Some backends require TLS; use grpcs:// if plain grpc:// doesn't work.
See CONTRIBUTING.md.
Apache-2.0. See LICENSE.