docs/conventions/ carries eighteen conventions and none of them governs how a plugin names, keys,
retains, or overwrites what it writes under ${CLAUDE_PLUGIN_DATA}. The hazard is recognized inside
the fleet — two claude-config surfaces write the rationale out in full — and applied inconsistently
even within one plugin, which is the signature of a missing rule rather than a per-skill oversight.
Without a convention the next report-writing skill reproduces it, and the two open adoptions (filed
separately in this batch, one on claude-config:audit-instructions and one on claude-memory:audit)
have no doctrine to point at.
Rows
Evidence
RKD-04 — a verified absence
ls docs/conventions/ returns eighteen entries at HEAD.
grep -rn "CLAUDE_PLUGIN_DATA\|plugin data" docs/conventions/ returns hits in three files, none
governing keying:
docs/conventions/hook-config-delivery/README.md — a persistence channel for hook config.
docs/conventions/topic-docs/README.md — a tier table ("Machine state | ${CLAUDE_PLUGIN_DATA} … Never committed"), tier placement, per-plugin exemptions, and a non-interactive fallback root ${CLAUDE_PLUGIN_DATA}/topic-docs/<slug>/ keyed by topic slug, not project — an instance of the same gap, not a rule that closes it.
docs/conventions/topic-docs/CHANGELOG.md — the same tier language.
The nearest governing text is docs/MIGRATION-PLAYBOOK.md:256-258, [SPEC]-marked:
"${CLAUDE_PLUGIN_DATA} for machine state only. [SPEC] … Never a channel for consumer
configuration…"
That governs what may live there. It says nothing about naming, keying, retention, or overwrite.
The hazard is already written out, twice, inside one plugin — and applied to some of its skills and
not others. Both verified verbatim at HEAD:
plugins/claude-config/skills/unhobble/SKILL.md:53-62 — "${CLAUDE_PLUGIN_DATA} is machine-global, so two checkouts sharing a basename…"
plugins/claude-config/skills/audit-pass/reference/run-state-and-resumability.md:10-20 — "machine-global, not per-project, so state keyed by working directory would collide or fragment", with <state-key> = <repo-identity>/<worktree-discriminator> specified in full.
audit-pass keys correctly. #2250 has since moved audit-prompting-postures onto the same scheme.
audit-instructions and claude-memory:audit still write a fixed filename. Four writers, one plugin
family, two doctrines — that inconsistency is what a convention exists to remove.
Upstream is silent, not permissive. https://code.claude.com/docs/en/plugins-reference §
Persistent data directory, fetched 2026-08-11:
"The ${CLAUDE_PLUGIN_DATA} directory resolves to ~/.claude/plugins/data/{id}/, where {id} is
the plugin identifier with characters outside a-z, A-Z, 0-9, _, and - replaced by -."
No project segment in the formula, and reports/history are not among the documented intended uses
("installed dependencies such as node_modules … generated code, and caches"). Nothing to wait for.
Two further docs facts the convention should encode:
"The data directory is deleted automatically when you uninstall the plugin from the last scope where
it is installed. … The CLI deletes by default; pass --keep-data to preserve it."
— uninstall-fragility argues against unbounded per-project report trees and for the machine-health
pattern: one file per run plus an appended history line, with a stable "read the last audit" path.
See plugins/machine-health/skills/audit/SKILL.md:73-75, verified verbatim ("one file per run, so a
same-day rerun does not overwrite the earlier report"; state/history.jsonl, "the trend source of
truth"). That pattern closes data loss but not a read-back path — a skill that serves its stored
report back needs project identity, not merely non-destructive history. The convention has to say both.
RKD-06 — the "looks scoped but isn't" case
plugins/bug-report/skills/write/SKILL.md:97 at HEAD:
2. Otherwise, write to `${CLAUDE_PLUGIN_DATA}/bug-reports/<project-slug>/`, where `<project-slug>` is the kebab-cased basename of the project root (`${CLAUDE_PROJECT_DIR}`, or the git toplevel when unset). The plugin data directory is per-plugin, not per-project — without the slug, Step 2's duplicate scan would match another repository's report on the same symbol.
The line already states the hazard and already names the reason — it just picks a colliding key. Two
same-named checkouts (a fork, a same-named worktree) share one slug directory and the duplicate scan
then cross-matches between them. It escapes overwrite only because its filenames are timestamped.
plugins/bug-report/skills/setup/SKILL.md:29,32 restates the same key.
The originating item filed this explicitly as "context, not a defect to fix." Honor that. It belongs
in the convention as the worked example of a key that looks scoped and is not, next to the
unhobble:55 sentence that names the same insufficiency. Filing it as a bug-report plugin defect
would misrepresent the source.
Where a new convention has to register
docs/PLUGIN-PHILOSOPHY.md:468-489 enumerates all eighteen current conventions by link and is the
registration surface — and the collision magnet for any other item adding a convention.
docs/MIGRATION-PLAYBOOK.md:227-230 names the authoring template (docs/conventions/hook-telemetry/)
and the versioned-contract requirement; scripts/check-changelog-parity.sh exists, so check whether a
convention CHANGELOG.md is gated before authoring.
Adjacent, deliberately not duplicated
Provenance
Severity: MED · Provenance: SELF_REPORTED. The convention-absence sweep came from a fresh-context discovery:explore pass, but the originating item's own instruction — run /plugin-quality:audit against the named components before filing — was never carried out. The absence, the eighteen-entry listing, the greps, and the doc quotes were independently re-verified against HEAD by the validation pass.
Origin: handoff-inbox item 2026-08-10-claude-config-report-keying-and-dispatch
Ledger: .work/handoff-inbox-batch-4/ledgers/I6-report-keying-dispatch.md § RKD-04, RKD-06
Verified against repo HEAD 9b34a82a.
docs/conventions/carries eighteen conventions and none of them governs how a plugin names, keys,retains, or overwrites what it writes under
${CLAUDE_PLUGIN_DATA}. The hazard is recognized insidethe fleet — two
claude-configsurfaces write the rationale out in full — and applied inconsistentlyeven within one plugin, which is the signature of a missing rule rather than a per-skill oversight.
Without a convention the next report-writing skill reproduces it, and the two open adoptions (filed
separately in this batch, one on
claude-config:audit-instructionsand one onclaude-memory:audit)have no doctrine to point at.
Rows
docs/conventions/(verified absence, 18 entries) — no convention governs report filename, keying, history retention, or overwrite under${CLAUDE_PLUGIN_DATA}; the nearest governing text scopes what may live there, not how it is named.plugins/bug-report/skills/write/SKILL.md:97— the "looks scoped but isn't" case: keying on the kebab-cased basename of the project root, which the fleet's own text names as insufficient. Carry it into the convention as the named example; this is not filed as abug-reportdefect and must not become one.Evidence
RKD-04 — a verified absence
ls docs/conventions/returns eighteen entries at HEAD.grep -rn "CLAUDE_PLUGIN_DATA\|plugin data" docs/conventions/returns hits in three files, nonegoverning keying:
docs/conventions/hook-config-delivery/README.md— a persistence channel for hook config.docs/conventions/topic-docs/README.md— a tier table ("Machine state |${CLAUDE_PLUGIN_DATA}… Never committed"), tier placement, per-plugin exemptions, and a non-interactive fallback root${CLAUDE_PLUGIN_DATA}/topic-docs/<slug>/keyed by topic slug, not project — an instance of the same gap, not a rule that closes it.docs/conventions/topic-docs/CHANGELOG.md— the same tier language.The nearest governing text is
docs/MIGRATION-PLAYBOOK.md:256-258,[SPEC]-marked:That governs what may live there. It says nothing about naming, keying, retention, or overwrite.
The hazard is already written out, twice, inside one plugin — and applied to some of its skills and
not others. Both verified verbatim at HEAD:
plugins/claude-config/skills/unhobble/SKILL.md:53-62— "${CLAUDE_PLUGIN_DATA}is machine-global, so two checkouts sharing a basename…"plugins/claude-config/skills/audit-pass/reference/run-state-and-resumability.md:10-20— "machine-global, not per-project, so state keyed by working directory would collide or fragment", with<state-key>=<repo-identity>/<worktree-discriminator>specified in full.audit-passkeys correctly. #2250 has since movedaudit-prompting-posturesonto the same scheme.audit-instructionsandclaude-memory:auditstill write a fixed filename. Four writers, one pluginfamily, two doctrines — that inconsistency is what a convention exists to remove.
Upstream is silent, not permissive. https://code.claude.com/docs/en/plugins-reference §
Persistent data directory, fetched 2026-08-11:
No project segment in the formula, and reports/history are not among the documented intended uses
("installed dependencies such as
node_modules… generated code, and caches"). Nothing to wait for.Two further docs facts the convention should encode:
— uninstall-fragility argues against unbounded per-project report trees and for the machine-health
pattern: one file per run plus an appended history line, with a stable "read the last audit" path.
See
plugins/machine-health/skills/audit/SKILL.md:73-75, verified verbatim ("one file per run, so asame-day rerun does not overwrite the earlier report";
state/history.jsonl, "the trend source oftruth"). That pattern closes data loss but not a read-back path — a skill that serves its stored
report back needs project identity, not merely non-destructive history. The convention has to say both.
RKD-06 — the "looks scoped but isn't" case
plugins/bug-report/skills/write/SKILL.md:97at HEAD:The line already states the hazard and already names the reason — it just picks a colliding key. Two
same-named checkouts (a fork, a same-named worktree) share one slug directory and the duplicate scan
then cross-matches between them. It escapes overwrite only because its filenames are timestamped.
plugins/bug-report/skills/setup/SKILL.md:29,32restates the same key.The originating item filed this explicitly as "context, not a defect to fix." Honor that. It belongs
in the convention as the worked example of a key that looks scoped and is not, next to the
unhobble:55sentence that names the same insufficiency. Filing it as abug-reportplugin defectwould misrepresent the source.
Where a new convention has to register
docs/PLUGIN-PHILOSOPHY.md:468-489enumerates all eighteen current conventions by link and is theregistration surface — and the collision magnet for any other item adding a convention.
docs/MIGRATION-PLAYBOOK.md:227-230names the authoring template (docs/conventions/hook-telemetry/)and the versioned-contract requirement;
scripts/check-changelog-parity.shexists, so check whether aconvention
CHANGELOG.mdis gated before authoring.Adjacent, deliberately not duplicated
${CLAUDE_*}substitution-semantics issues, not keying.Provenance
Severity: MED · Provenance: SELF_REPORTED. The convention-absence sweep came from a fresh-context
discovery:explorepass, but the originating item's own instruction — run/plugin-quality:auditagainst the named components before filing — was never carried out. The absence, the eighteen-entry listing, the greps, and the doc quotes were independently re-verified against HEAD by the validation pass.Origin: handoff-inbox item
2026-08-10-claude-config-report-keying-and-dispatchLedger:
.work/handoff-inbox-batch-4/ledgers/I6-report-keying-dispatch.md§ RKD-04, RKD-06Verified against repo HEAD
9b34a82a.