Skip to content

skill-stocktake: scan.sh counts trashed skills as live, and reports unmeasured usage as 0 #3218

Description

@mattspruiell

scripts/scan.sh has two defects that let a stocktake report confident results from data it does not have. Both are in the current main (211-line scan.sh). Related to #2598 and #2801, which cover other traversal defects in the same file.

1. Trashed skills are counted as live

scan_dir_to_json() runs find -L "$dir" -name "SKILL.md" -type f with no exclusion for .trash/. On a machine where skills have been removed through the skills CLI, ~/.claude/skills/.trash/<timestamp>-<pid>-<rand>/<name>/SKILL.md still matches.

Observed on one machine: the scan reported 179 skills where 117 were live. 62 were trashed copies.

Three consequences. The inventory overstates the skill count by however many have been removed. Evaluation budget is spent writing verdicts on skills the user already deleted. And because several trashed copies share a name with a live skill, the "content overlap with other skills" checklist item flags skills as overlapping with their own tombstones.

It also corrupts the cache. Those 62 entries are written into results.json, so every subsequent Quick Scan diffs against them.

Suggested fix: add -not -path '*/.trash/*' to the find in scan_dir_to_json(), in both scan.sh and quick-diff.sh.

2. Unmeasured usage is reported as 0

count_obs() returns 0 when $OBSERVATIONS does not exist:

count_obs() {
  local file="$1" cutoff="$2"
  if [[ ! -f "$OBSERVATIONS" ]]; then
    echo 0
    return

~/.claude/observations.jsonl is not created by Claude Code by default, so on a stock install every skill's use_7d and use_30d are 0. The aggregated fast path defaults to 0 the same way.

SKILL.md then renders those as "7d use" and "30d use" columns in the Phase 1 inventory table, and the Phase 2 checklist lists "Usage frequency considered" as an item to be satisfied. Nothing anywhere states that the file may be absent or that the columns may be meaningless — the words "observations.jsonl" and "unmeasured" do not appear in SKILL.md at all.

The result is that a check which could not run is indistinguishable from a check that ran and found zero usage, and a reviewer can reasonably retire a skill on evidence that does not exist.

Suggested fix: emit null or the string unmeasured rather than 0 when the file is absent; render that distinctly in the inventory table; and add a line to SKILL.md stating that absent usage data is never evidence for a verdict.

Why these belong together

Both are the same failure: absent data presented as measured data. The first inflates a count, the second fabricates a zero. Either alone produces a stocktake that looks complete and is not.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions