CI workflows
All workflow files live under
.github/workflows/.
PR approval labels
The following workflows work together to automatically manage approval-related labels on pull requests:
| Workflow file | Trigger | Privileges |
|---|---|---|
pr-review-trigger.yml | pull_request_review | Minimal (no secrets) |
pr-approval-labels.yml | pull_request_target, workflow_run | App token for label edits and org/team reads |
blog-publish-labels.yml | schedule (daily 7 AM UTC) | App token + SLACK_WEBHOOK_URL secret |
Labels managed
missing:docs-approval— added when approval from thedocs-approversteam is pending; removed once a docs-approver approves.missing:sig-approval— added when approval from a SIG team is pending (determined by files changed and.github/component-owners.yml); removed once a SIG member approves or when no SIG component is touched.ready-to-be-merged— added when all required approvals are present; removed otherwise. For PRs carrying any label inPUBLISH_DATE_LABELS(currently:blog,announcements), this label is also gated on the publish date found in changed files.
Publish date gating
The script scans each changed file for a line beginning with date: (typically
from the front matter in Markdown content). If it finds a date in the future,
the ready-to-be-merged label is withheld until that date arrives (UTC). This
helps prevent content from being merged before its scheduled publication date.
The check applies to PRs carrying any label listed in the PUBLISH_DATE_LABELS
array in the script (currently: blog and announcements). Adding a label to
that array extends the check to other PR types.
If a PR contains multiple files with different dates, the label is gated on the latest date — all content must be ready before merging.
Script operating modes
The script runs in one of two modes, selected by whether the PR environment
variable is set:
- Single-PR mode — processes a single PR. Used by the
pull_request_targetandworkflow_runtriggers. - Batch mode — queries GitHub for all open PRs carrying any
PUBLISH_DATE_LABELSlabel and processes each one. Used by theblog-publish-labels.ymlscheduletrigger (daily at 7 AM UTC), so a PR whose publish date arrives overnight receivesready-to-be-mergedautomatically without requiring a new commit.
Why two workflows?
GitHub’s pull_request_review event has no _target variant. This means a
workflow triggered by a review on a fork PR runs in the fork’s context and
cannot access the base repository’s secrets.
To work around this limitation, the system uses a
workflow_run chaining pattern:
pr-review-triggerruns on every review submission/dismissal. It saves the PR number as an artifact and exits — no secrets needed.pr-approval-labelsis triggered byworkflow_run(when the trigger workflow completes). It runs in the base repository context with full access to the GitHub App token, downloads the artifact, and updates labels.
For content changes (opened, reopened, synchronize), the
pr-approval-labels workflow is triggered directly via pull_request_target.
sequenceDiagram
participant R as Reviewer
participant GH as GitHub
participant T as pr-review-trigger
participant L as pr-approval-labels
R->>GH: Submits review (approve/request changes/dismiss)
Note over GH: pull_request_review event
GH->>T: Trigger (fork context, no secrets)
T->>T: Save PR number as artifact
T->>GH: Upload artifact, workflow completes
Note over GH: workflow_run event (completed)
GH->>L: Trigger (base repository context, with secrets)
L->>L: Download PR number artifact
L->>L: Run pr-approval-labels.sh
L->>GH: Add/remove labelssequenceDiagram
participant A as Author
participant GH as GitHub
participant L as pr-approval-labels
A->>GH: Opens/updates PR
Note over GH: pull_request_target event
GH->>L: Trigger directly (base repo context, with secrets)
L->>L: Run pr-approval-labels.sh
L->>GH: Add/remove labelsSecurity model
pr-review-trigger: intentionally minimal — no secrets, no privileged permissions. Ignoresreview.state == "commented"since comments don’t affect approvals.pr-approval-labels: runs with a GitHub App token (OTELBOT_DOCS_APP_ID/OTELBOT_DOCS_PRIVATE_KEY) that has permissions to read org/team membership and edit PR labels. Usespull_request_targetandworkflow_runto ensure it always executes in the trusted base repository context.blog-publish-labels: runs on a schedule with a GitHub App token and theSLACK_WEBHOOK_URLsecret. Always executes in the trusted base repository context (schedule events have no fork variant).
Blog publish labels
The blog-publish-labels.yml workflow runs daily at 7 AM UTC. It
executes pr-approval-labels.sh in batch mode —
checking all open PRs with blog or announcements labels — and posts a Slack
notification when ready-to-be-merged is newly applied to any of them. You can
also trigger it manually via workflow_dispatch with a force_notify input to
send a test Slack notification without waiting for a label change on a PR.
| Workflow file | Trigger | Secrets required |
|---|---|---|
blog-publish-labels.yml | schedule (daily 7 AM UTC), workflow_dispatch (manual test via force_notify) | OTELBOT_DOCS_PRIVATE_KEY, SLACK_WEBHOOK_URL |
The Slack notification fires only when the label transitions from absent to
present on that run — repeated daily runs for an already-labeled PR do not
re-notify. When triggering the workflow manually, set force_notify to true
to force a one-off test notification so you can verify the Slack formatting.
Slack webhook setup
The workflow uses a Slack Workflow Builder webhook trigger, which allows non-engineers to own the message format without touching workflow code.
Create the webhook:
In Slack: Tools → Workflow Builder → New Workflow → Start from scratch
Choose trigger: Webhook
Declare one variable — name:
pr_list, type: TextAdd a step: Send a message to the desired channel, with body:
:newspaper: *Blog posts ready to publish* The following PRs have reached their publish date and all required approvals — they are ready to be merged: {{pr_list}} Have a great day! :sunny:Then click Add button and configure:
- Label:
Review and merge - Color: Primary (green)
- Action: Open a link
- URL:
https://github.com/open-telemetry/opentelemetry.io/issues?q=is%3Apr+state%3Aopen+label%3Ablog+label%3Aready-to-be-merged
- Label:
Publish the workflow and copy the webhook URL
Add it to the repository: Settings → Secrets and variables → Actions → New repository secret, name:
SLACK_WEBHOOK_URL
Payload sent by the workflow:
{
"pr_list": "#123: Add blog post: OTel 1.0\nhttps://github.com/.../pull/123\n\n#456: Announce: new SIG\nhttps://github.com/.../pull/456"
}
Each PR is listed as a title line followed by its URL on the next line, with a blank line between entries. Slack auto-links bare URLs. Multiple PRs labeled on the same day are batched into a single message — one webhook call regardless of how many PRs are ready.
sequenceDiagram
participant GH as GitHub
participant B as blog-publish-labels
participant API as GitHub API
participant S as Slack
Note over GH: schedule event (daily, 7 AM UTC)
GH->>B: Trigger (base repository context, with secrets)
B->>API: Query open PRs with PUBLISH_DATE_LABELS labels
API-->>B: List of PRs
loop Each PR
B->>B: Run pr-approval-labels.sh (batch mode)
B->>GH: Add/remove labels
end
alt Any PR newly labeled ready-to-be-merged
B->>S: POST Slack notification with PR links
endPR fix directives
The pr-actions.yml workflow lets contributors run selected fix
scripts by commenting on a PR:
/fixrunsnpm run fix./fix:<name>runsnpm run fix:<name>(for example,/fix:format)./fix:allis mapped to/fixsince the command semantics changed (#9291)./fix:ALLis mapped tofix:allso that maintainers can runfix:all.
It runs as a two-stage pipeline:
generate-patch(untrusted): checks out the PR branch, runs the fix command, prunes the link refcache, and uploads a patch artifact (pr-fix.patch), up to 1024 KB.apply-patch(trusted): runs with a GitHub App token, applies the patch, and pushes a commit to the PR branch.
If a directive produces no changes, a separate notify-noop job comments that
nothing needed to be committed.
Other workflows
The repository includes several other workflows:
| Workflow | Purpose |
|---|---|
check-links.yml | Sharded link checking using htmltest |
check-text.yml | Textlint terminology checks |
check-i18n.yml | Localization front matter validation |
check-spelling.yml | Spell checking |
auto-update-registry.yml | Auto-update registry package versions |
auto-update-versions.yml | Auto-update OTel component versions |
build-dev.yml | Development build and preview |
label-prs.yml | Auto-label PRs based on file paths |
component-owners.yml | Assign reviewers based on component ownership |