Published by the Skilled project. Skilled reports recognized skill calls from local coding-agent histories. This diagnostic works without installing it.
On 17 September 2026, GitHub added Copilot CLI skill totals to the usage metrics API. The report answers "which items are used most" with a top-five list. For privacy, customer-defined skill names are not shown. Those skills are grouped under other. Source: Agentic CLI customizations now in the usage metrics API.
That is useful for GitHub-provided skills. It is not a named-use signal for a skill you shipped yourself.
The same gap shows up elsewhere. Marketplace install counts record copies, not runs. Server-side MCP or API skills can close the loop on the author's infrastructure. A local SKILL.md playbook often cannot. Yesterday's Ask HN asked how to measure runtime use of shipped skills that never call home. This note is a local answer that a user can run and, if they choose, share without transcripts.
Four columns #
Keep one row per skill you care about:
1skill | public label | local named invocations | last used
- skill: the name in
SKILL.mdfrontmatter, sanitized. No paths, prompts, or session ids. - public label: what an external aggregate would show. For Copilot CLI org totals that is the published GitHub name, or
other. For a marketplace it is the install count, not a run count. - local named invocations: retained records that carry the skill's own name, using the harness-specific definition below.
- last used: date of the newest named record, or
none.
A useful report is one completed row, especially public label=other / local named invocations=0 or public label=other / local named invocations>=1.
What counts as a named invocation #
These are the records Skilled's readers currently keep. They are not a complete inventory of installed skills, and they are not proof the workflow succeeded.
| Harness | Named invocation | Not a named invocation |
|---|---|---|
| Claude Code | Skill tool_use with input.skill, or a non-builtin /name in history.jsonl |
A file under ~/.claude/skills/ or ~/.claude/skills/synced/. A Read of SKILL.md. /skills. |
| Codex CLI | <name> inside a skill block on a response_item |
A copied parent session_meta on a fork file. Built-ins such as skill-creator. |
| OpenCode | completed part rows with tool=skill and state.input.name |
A folder listing. <available_skills> XML. A slash-typed command. |
| Grok CLI | read_file of skills/<name>/SKILL.md, or a <command-name> slash |
grok inspect discovery. A read of a sibling file in the skill folder. tool_call_update repeats. |
| Droid CLI | tool_result text matching Skill "name" is now active |
rg of the skill name in session JSONL. /skills Effective status. disabledSkills. |
Skilled does not read GitHub Copilot CLI session-state or org reports. If Copilot CLI is the only harness on the machine, this local column is out of scope.
Synthetic check #
The example is synthetic. It reads no files, makes no network requests, and uses only Python's standard library. Save it as named_vs_other.py and run python3 named_vs_other.py.
1def row(skill, github_provided, installed, named_events):
2 public = skill if github_provided else "other"
3 named = len(named_events)
4 last = named_events[-1] if named_events else "none"
5 return (skill, public, named, last)
6
7# Custom skill: one install, no retained Skill tool_use.
8disk_only = row("release-notes", False, True, [])
9# Custom skill: one install, one retained Skill tool_use.
10invoked = row("release-notes", False, True, ["2026-09-17"])
11# GitHub-provided skill: the org report can print the name.
12builtin = row("code-review", True, True, ["2026-09-17"])
13
14assert disk_only == ("release-notes", "other", 0, "none")
15assert invoked == ("release-notes", "other", 1, "2026-09-17")
16assert builtin == ("code-review", "code-review", 1, "2026-09-17")
17
18for label, result in [
19 ("custom, disk only", disk_only),
20 ("custom, one named call", invoked),
21 ("github-provided", builtin),
22]:
23 print(label, result)
Expected output:
1custom, disk only ('release-notes', 'other', 0, 'none')
2custom, one named call ('release-notes', 'other', 1, '2026-09-17')
3github-provided ('code-review', 'code-review', 1, '2026-09-17')
Install state is the same in the first two rows. The org label is other in both. Only the retained named event changes the third column. That is the gap marketplace install counts and Copilot CLI other totals cannot close.
A no-install Claude Code check for the named column, on a machine that already has session JSONL, is: look for assistant tool_use blocks with "name": "Skill" and read input.skill. Do not paste the files. Count the name. Zero is inconclusive if history was pruned, rotated, or stored in a format the check does not recognize.
What Skilled can fill #
Inspected implementation: Skilled v0.3.3, src/providers/claude-code.ts, codex.ts, opencode.ts, grok.ts, droid.ts, and src/cli.ts. An isolated Claude Code fixture with a disposable CLAUDE_CONFIG_DIR (no real histories) returned 0 calls from a SKILL.md on disk plus a Read of that path, and 1 release-notes call from a single Skill tool_use. That tests the reader on artificial records, not compatibility with every Claude Code release.
If Skilled is already installed and the machine has one of the five harnesses above:
1skilled calls --json --no-index
Filter to one name with skilled detail release-notes --json --no-index. Add --source claude-code (or codex, opencode, grok, droid) when several agents are present. The command does not talk to GitHub, does not read Copilot CLI, and does not judge whether the skill's steps were followed.
Limits #
- A named invocation is not selection quality, task success, cost, or causation.
- Missing, pruned, or unrecognized history makes zero inconclusive.
- Copilot CLI org totals remain the right place for GitHub-provided names and for org-level
othervolume. This check does not replace them. - Do not send session transcripts, repo paths, or prompts to a skill author. The four-column row is the whole payload.
If you run this on a custom skill that GitHub would group as other, a useful reply is one sanitized row. If you tried Skilled because of this page, say so separately. Running the Python example is not evidence of adoption.