From opentelemetry-docs-skills
Batch triage GitHub issues for any repository (defaults to open-telemetry/opentelemetry.io). Analyzes staleness, duplicates, codebase changes, and related PRs to produce actionable triage reports with ready-to-paste gh commands. Read-only — never modifies GitHub.
How this skill is triggered — by the user, by Claude, or both
Slash command
/opentelemetry-docs-skills:otel-triageopusThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Batch-triage untouched GitHub issues in any GitHub repository. Produces a
Batch-triage untouched GitHub issues in any GitHub repository. Produces a
structured report with per-issue dossiers, confidence tiers, and ready-to-paste
gh commands. Defaults to open-telemetry/opentelemetry.io when --repo is
omitted.
This skill is read-only. It produces recommendations and gh commands but
never executes commands that modify GitHub state. The human reviewer
decides which commands to run.
/otel-triage [--repo OWNER/REPO] [--count N] [--fresh] [--pending] [--reanalyze 1234,5678] [--type docs|bug|feat|blog|feedback] [--profile NAME[,NAME2]]
| Flag | Default | Purpose |
|---|---|---|
--repo OWNER/REPO | from active repo profile, or open-telemetry/opentelemetry.io | Target GitHub repository |
--count N | 10 | Number of issues to process |
--fresh | off | Ignore state entirely — re-analyze all fetched issues |
--pending | off | Only surface issues that were analyzed but never executed (outcome absent) |
--reanalyze 1234,5678 | — | Force re-analysis of specific issue numbers, ignoring their state entry |
--type | — | Filter by issue type prefix (requires a repo profile with type_filters defined) |
--profile NAME[,NAME2] | — | Load named profile(s), comma-separated or repeated. Merged in order. |
Precedence: --fresh overrides --pending and --reanalyze. When
--reanalyze is set, only the listed issues are fetched and analyzed
(skips the broad fetch + filter pipeline).
Run this before anything else.
Gather all --profile values (flag may be repeated or comma-separated):
profiles_requested = split and flatten all --profile values
Determine the target repo (from --repo flag, or fall back to
open-telemetry/opentelemetry.io if omitted):
REPO = value of --repo flag, or "open-telemetry/opentelemetry.io"
Scan all *.yml files in the plugin's data/ directory.
If any profile has repo.auto_apply_for containing REPO, prepend it to
profiles_requested (unless already listed). This is how the
opentelemetry-website profile auto-activates when running against that repo.
For each name in profiles_requested:
# Resolve path
PROFILE_FILE=<PLUGIN_ROOT>/data/<NAME>.yml
# Check existence
ls "$PROFILE_FILE" 2>/dev/null || {
echo "Profile '<NAME>' not found. Available profiles:"
ls <PLUGIN_ROOT>/data/*.yml | xargs -I{} basename {} .yml
exit 1
}
Read and validate against
<PLUGIN_ROOT>/schemas/triage-profiles.schema.json.
Abort with a clear error if validation fails.
Merge all loaded profiles into a single PROFILE object:
repo section: last profile wins on key conflictsevaluation section: all are collected into a list (multiple evaluations
can stack — each produces its own assessment block per dossier)If PROFILE.repo.default_repo is set AND --repo was not explicitly provided:
REPO = PROFILE.repo.default_repo
Else:
REPO = value of --repo flag, or "open-telemetry/opentelemetry.io"
Pass REPO and the full merged PROFILE to every otel-issue-triager
subagent via <repo_profile> and <evaluation_profiles> XML blocks.
gh issue list \
--repo <REPO> \
--state open \
--json number,title,body,labels,createdAt,updatedAt,author,reactionGroups,comments \
--limit 200 \
--sort updated \
--order asc
When --label is provided with comma-separated values (e.g.,
--label "good first issue,bug"), expand into multiple --label flags — gh
treats them as AND (issue must have all labels):
gh issue list --repo <REPO> --label "good first issue" --label "bug" ...
Critical: gh issue list --label does NOT support negation. Fetch broadly,
then filter client-side.
After fetching, filter with jq:
# Pipe the gh output through jq to remove already-triaged issues
... | jq '[.[] | select(
(.labels | map(.name) | any(startswith("triage:"))) | not
)]'
| User Flag | gh Flag | Post-Fetch Filter |
|---|---|---|
--label LABEL[,LABEL2] | --label LABEL [--label LABEL2] | AND filter — issues must have all specified labels. Comma-separate for multiple. |
--sig SIG | --label sig:SIG | — |
--since DATE | — | .createdAt >= DATE |
--type TYPE | — | Title prefix match (see below) |
--exclude LABEL | — | Exclude issues with that label |
Type → title prefix mapping:
Use PROFILE.repo.type_filters when defined. Each entry maps a --type
value to a title_prefix (and optionally a label_match for OR matching).
If no repo profile is active and --type is passed, warn the user that
--type requires a repo profile with type_filters configured.
Load .tasks/triage/state.json. For each fetched issue:
issueUpdatedAt matches current updatedAt,
AND outcome is set → already triaged and executedissueUpdatedAt matches, but outcome
is absent → already analyzed but not yet acted on. Surface to reviewer as
"awaiting action" instead of re-analyzingissueUpdatedAt < updatedAt → new activity
since last triage (clear stale outcome if present)Flag overrides:
--fresh → skip all state filtering, re-analyze everything--pending → only return issues in "pending execution" state (analyzed,
no outcome). Useful for reviewing what still needs action--reanalyze 1234,5678 → fetch only those issues by number via
gh issue view, bypass state check, and re-analyze them. Clears any
existing outcome in state on writeSort remaining issues by updatedAt ascending (stalest first). Take the
first N issues per --count.
Group issues for analysis (especially relevant for --parallel mode).
If an issue has a sig:* label, group by that SIG.
Scan title and body for signals:
SIG keywords (only when PROFILE.repo.sig_keywords is defined):
Apply each entry from PROFILE.repo.sig_keywords — if any keyword from
the entry matches the issue title or body, assign the corresponding label.
If ambiguous (multiple SIGs match), flag as "multi-SIG" and set confidence
to LOW.
When no sig_keywords are defined in the active profile, skip keyword
inference. Group by existing sig:*-style labels if present; otherwise fall
back to content-type inference and uncategorized.
Content type inference:
| Signal | Category |
|---|---|
Title starts with [Docs]: or path content/en/docs/ | docs |
Title starts with blog: or path content/en/blog/ | blog |
Path data/registry/ or mentions registry | registry |
Path .github/ or scripts/ | CI/infra |
Title starts with page feedback: | feedback |
Resulting category buckets: Use PROFILE.repo.category_buckets if
defined. Default fallback: sig:* (from existing labels), docs-general,
blog, registry, ci-infra, feedback, uncategorized.
For each issue, gather these signals:
Infer from title prefix and body structure:
| Prefix | Type |
|---|---|
[Docs]: | Documentation update |
bug: | Bug report |
feat: | Feature request |
blog: | Blog proposal |
page feedback: | Page feedback |
| (none) | Infer from body template structure |
# Days since last update
# Calculate from updatedAt field
| Tier | Days Since Last Update |
|---|---|
| Critical | > 180 days |
| High | 90–180 days |
| Medium | 30–90 days |
| Low | < 30 days |
Check:
gh api "repos/<REPO>/issues?creator=<username>&state=all&per_page=5&sort=updated" \
--jq '.[0].updated_at'
Extract file paths mentioned in the issue body. For each:
# Check if file exists
ls <file-path> 2>/dev/null
# Check git history since issue was opened
git log --oneline -10 --since="<issue-created-date>" -- "<file-path>"
Report:
Read file content when needed to verify if the issue's request has been fulfilled.
# PRs referencing this issue
gh pr list --repo <REPO> --state all \
--search "<issue-number>" --json number,title,state,mergedAt,url --limit 10
Check if any merged PRs address the issue.
Parse issue body for links to github.com/open-telemetry/* repos. For each:
gh issue view <URL> --json number,title,state,labels 2>/dev/null || \
gh pr view <URL> --json number,title,state,mergedAt 2>/dev/null
Non-OTel external links: list them, note "external — manual review needed".
# Search for similar open issues
gh issue list --repo <REPO> \
--state open --search "<key terms from title>" \
--json number,title,labels --limit 10
If --include-closed is set:
gh issue list --repo <REPO> \
--state closed --search "<key terms>" \
--json number,title,labels --limit 10
Similarity signals: title keyword overlap, same referenced files, same described symptoms.
Confidence tiers:
| Tier | Criteria |
|---|---|
| HIGH | File deleted/renamed; exact duplicate; >1 year with zero engagement; merged PR resolves it; author confirmed fixed |
| MEDIUM | Clear classification possible; reasonable action path; related PRs exist; active author |
| LOW | Ambiguous description; cross-cutting; external blockers; high engagement needing judgment |
Triage actions:
| Action | When |
|---|---|
close:stale | No activity, referenced content updated, likely resolved |
close:duplicate | Near-duplicate of #XXXX |
close:wontfix | Out of scope or superseded |
close:invalid | Spam, unclear, not a real issue |
label:triage:accepted:needs-pr | Valid, actionable, needs contributor |
label:triage:accepted | Valid, may have someone on it |
label:triage:deciding:needs-info | Missing details from reporter |
label:triage:deciding:blocked | Blocked on external dependency |
label:triage:deciding | Needs maintainer discussion |
label:good-first-issue | Small, well-scoped onboarding task |
add-labels | Only needs SIG/component/area labels |
Save to .tasks/triage/reports/triage-YYYY-MM-DD-Nissues.md where N is the
number of issues analyzed (e.g., triage-2026-04-01-10issues.md). If a file
with that exact name exists, append a timestamp:
triage-YYYY-MM-DD-Nissues-HHmm.md.
# Triage Report — YYYY-MM-DD
**Issues analyzed**: N
**Filters applied**: (list active filters)
**Profiles active**: (list profile names, or "none")
**Sorting**: staleness-first (oldest updated → newest)
## Summary
| Action | High | Medium | Low | Total |
|--------|------|--------|-----|-------|
| Close (stale) | X | X | X | X |
| Close (duplicate) | X | X | X | X |
| Close (wontfix) | X | X | X | X |
| Accept (needs-pr) | X | X | X | X |
| Accept | X | X | X | X |
| Needs info | X | X | X | X |
| Deciding | X | X | X | X |
| Add labels only | X | X | X | X |
### By Category
| Category | Issues | Closeable | Actionable | Needs Review |
|----------|--------|-----------|------------|--------------|
| sig:collector | X | X | X | X |
| docs-general | X | X | X | X |
| ... | ... | ... | ... | ... |
<!-- Render one block per evaluation profile. Omit section if no evaluation profiles active. -->
## Profile Assessment: {evaluation_profile.name}
> {evaluation_profile.report_note}
| Verdict | Count |
|---------|-------|
| Recommended | X |
| Maybe | X |
| Not suitable | X |
---
## HIGH Confidence — Auto-actionable
### [#1234](https://github.com/<REPO>/issues/1234) — [Docs]: Update SDK configuration page
- **Type**: docs | **Created**: 2024-01-15 | **Last updated**: 2024-02-01
- **Author**: @username (last active: 2025-12-01) | **Reactions**: 2 👍
- **Comments**: 3 (last: 2024-03-01)
- **Staleness**: Critical (400+ days)
- **Current labels**: `docs`
**Content Summary:**
One-paragraph summary of what the issue requests.
**Codebase Analysis:**
- Referenced file `content/en/docs/languages/go/configuration.md`:
- Status: exists
- Commits since issue opened: 12
- Assessment: content was rewritten in [#4567](https://github.com/<REPO>/pull/4567), appears to address this issue
**Related PRs:**
- [#4567](https://github.com/<REPO>/pull/4567) (merged 2024-06-15): "Rewrite Go config docs" — likely resolves this
**Linked Issues:**
- (none)
**Duplicate Candidates:**
- (none)
**Recommendation:**
- **Action**: `close:stale`
- **Confidence**: HIGH
- **Rationale**: Referenced file was rewritten in [#4567](https://github.com/<REPO>/pull/4567). No activity in 12+
months. Content now covers the requested topic.
- **Suggested labels**: `sig:go`, `docs`
**Suggested Comment:**
> This issue appears to have been addressed by subsequent updates to the
> configuration documentation (see PR #4567). Closing as resolved. If you
> believe this is still an issue, please reopen with updated details.
**Commands:**
```bash
gh issue comment 1234 -R <REPO> --body "This issue appears to have been addressed by subsequent updates to the configuration documentation (see PR #4567). Closing as resolved. If you believe this is still an issue, please reopen with updated details."
gh issue edit 1234 -R <REPO> --add-label "sig:go,docs"
gh issue close 1234 -R <REPO> --reason "not planned"
(same dossier format, recommendation less certain)
(same dossier format, multiple possible actions noted)
### Link Format Rules
All issue and PR references in the report MUST be clickable markdown links:
- **Issue headings**: `[#123](https://github.com/<REPO>/issues/123)`
- **PR references**: `[#456](https://github.com/<REPO>/pull/456)`
- **Cross-repo issues**: `[repo#789](https://github.com/open-telemetry/repo/issues/789)`
- **Cross-repo PRs**: `[repo#789](https://github.com/open-telemetry/repo/pull/789)`
Never use bare `#123` references in the report — always wrap in a markdown
link so the report is navigable from any markdown viewer.
---
## Phase 5: Update State
Write/update `.tasks/triage/state.json` (schema:
`~/.claude/plugins/local/otel-contributor/schemas/triage-state.schema.json`).
### Fields
| Field | Set by | Required | Purpose |
|-------|--------|----------|---------|
| `number` | Analysis | Yes | GitHub issue number |
| `decision` | Analysis | Yes | Recommended action (e.g., `close:stale`, `label:triage:accepted:needs-pr`) |
| `confidence` | Analysis | Yes | `HIGH`, `MEDIUM`, or `LOW` |
| `analyzedAt` | Analysis | Yes | When the issue was analyzed |
| `issueUpdatedAt` | Analysis | Yes | GitHub `updatedAt` at analysis time — used for change detection |
| `executedAt` | Execution | No | When the decision was acted on (comment/label/close) |
| `outcome` | Execution | No | What was actually done — may differ from `decision` on reviewer override |
| `note` | Execution | No | Rationale when outcome diverges from recommendation |
### Example
```json
{
"version": 1,
"lastRun": "2026-04-01T15:00:00-03:00",
"issues": {
"1234": {
"number": 1234,
"decision": "close:stale",
"confidence": "HIGH",
"analyzedAt": "2026-04-01T12:00:00-03:00",
"issueUpdatedAt": "2024-02-01T00:00:00Z",
"executedAt": "2026-04-02T10:00:00-03:00",
"outcome": "close:completed"
},
"5678": {
"number": 5678,
"decision": "label:triage:accepted:needs-pr",
"confidence": "HIGH",
"analyzedAt": "2026-04-01T12:00:00-03:00",
"issueUpdatedAt": "2025-01-20T08:41:07Z",
"executedAt": "2026-04-02T10:00:00-03:00",
"outcome": "label:triage:deciding:needs-info",
"note": "Upstream README uses nodes/pods not nodes/proxy — needs clarification before accepting"
}
}
}
number, decision, confidence, analyzedAt,
issueUpdatedAtexecutedAt, outcome, and
optionally note when the reviewer overrides the recommendationoutcome: "skipped" remain candidates for future triage runsissueUpdatedAt matches current GitHub updatedAt AND outcome
is set (already triaged and executed)updatedAt > stored issueUpdatedAt (new activity)decision is set but outcome is absent — surface
these to the reviewer as "awaiting action" instead of re-analyzingAlways use subagents for issue analysis. The orchestrator (main session)
handles fetching, filtering, categorization, report assembly, and state
management. The token-expensive per-issue analysis (reading full issue bodies,
comment threads, git log, PR searches, codebase cross-referencing) runs through
otel-issue-triager subagents on a cheaper model (Sonnet).
otel-issue-triager): spawn one agent per
category bucket. Each agent receives:
<repo_profile> XML block — the repo section of the merged PROFILE
(omit if no repo profile is active)<evaluation_profiles> XML block — list of all evaluation sections
from the merged PROFILE (omit if none)Aim for 3–5 agents. If there are many small buckets, merge related ones (e.g., multiple language SIGs into a "languages" bucket). If there are ≤3 issues total, a single agent is fine.
The per-issue analysis is the most token-intensive phase (~4K tokens per issue for fetching + cross-referencing). Running on Sonnet saves ~60% of token cost while maintaining high analysis quality — the work is mostly structured data extraction and pattern matching, not nuanced judgment. The orchestrator retains synthesis and decision-making on Opus.
Label suggestions come from PROFILE.repo.label_taxonomy when a repo profile
is active. Use only labels confirmed to exist in the target repo — do not
invent labels.
For repositories without a repo profile, fetch the actual label list:
gh label list --repo <REPO> --json name --limit 100 | jq -r '.[].name'
The built-in opentelemetry-website profile defines the full taxonomy for
open-telemetry/opentelemetry.io — see
data/opentelemetry-website.yml in the plugin root.
gh issue edit <number> --repo <REPO> \
--add-label "triage:accepted:needs-pr"
gh issue edit <number> --repo <REPO> \
--add-label "sig:collector,docs"
gh issue comment <number> --repo <REPO> \
--body "<comment text>"
gh issue close <number> --repo <REPO> \
--reason "not planned" \
--comment "<stale/wontfix notice>"
gh issue close <number> --repo <REPO> \
--reason "duplicate" \
--comment "Duplicate of #XXXX."
Comment templates come from PROFILE.repo.comment_templates when a repo
profile is active. The built-in opentelemetry-website profile provides
templates for stale, needs_info, duplicate, accepted, and
good_first_issue.
When no repo profile is active, use generic variants:
Stale:
This issue has had no activity for {N} months. Closing as stale. If still
relevant, please reopen with updated details.
Needs info:
Thank you for filing this issue. Could you provide more details?
- {specific missing info}
We'll revisit once more details are available.
Duplicate:
This appears to be a duplicate of #{duplicate_number}. Please check that
issue for updates. If your case is different, please reopen.
Accepted:
This issue has been triaged and accepted. It's ready for a contributor to
pick up.
Good first issue:
This issue has been triaged as a good first issue for new contributors.
npx claudepluginhub vitorvasc/opentelemetry-docs-skills --plugin opentelemetry-docs-skillsFetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Applies a firm's KYC/AML rules grid to parsed onboarding records: assigns risk rating, checks required documents, outputs rule outcomes with citations, and routes for escalation.
Generates daily or weekly digests of activity from connected sources (chat, email, docs, tasks, CRM), highlighting action items, decisions, mentions, and project updates.