Split out of #7654 (symptom 2) at dispatch time, per the lane ruling recorded there: the metadata half landed in packages/metadata-protocol, and this half lives in packages/mcp, which is domain:cli's lane. Filed unassigned with no domain:* label so triage routes it.
Blocked-by: #7654
The fact
PUT /api/v1/meta/skill/{name} with {active:true} returns 200, and the flip is not reflected over MCP prompts.
The two surfaces read different sources:
- The MCP prompt bridge's
listSkills calls metadataService.list('skill') — the registry/package listing, which carries no sys_metadata store overrides. Located in packages/mcp/src/mcp-server-runtime.ts as listSkills: async () => (await metadataService.list('skill')) ?? [] (anchor on the symbol, not the line — the file has moved under several merges). - The HTTP meta list goes through
protocol.getMetaItems, which merges the store overrides.
So a runtime override lands on the getMetaItems path and never on the path the bridge consumes.
What #7654 changed, and what it did NOT change
#7654 fixed the metadata half only — the duplicate-row defect on GET /api/v1/meta/skill. Measured there: getMetaItems ran two different merge implementations that disagreed about a package-less row, and the MetadataService merge now uses the same package-aware resolution as the overlay merge.
That fix does not change the bridge's answer at all, which is the question the triage comment on #7654 asked to have settled before this was treated as one bug. It does not, because the bridge never calls getMetaItems; it calls metadataService.list directly, one layer below where any overlay merging happens. The two symptoms shared a description, not a cause.
What #7654 does provide is the thing this card needs: a getMetaItems read that now returns exactly one correctly-merged row per skill name. That is why this is Blocked-by rather than independent — the merged read is worth pointing the bridge at only once it is correct, and as of #7654 it is.
Coordination — read this before starting
⚠️#6504 is open, assigned, and rewrites this same expression for a different reason. It quotes (await metadataService.list('skill')) ?? [] as one of its two measured consumers and proposes adding a listDiagnosed counterpart to IMetadataService so a known-partial answer stops presenting as a complete one.
Both cards change the same call with different intents, which is the failure mode where each side is individually green and the merged result is wrong. Whoever picks this up should re-read #6504's outcome first: if listDiagnosed has landed, express this change against the new method rather than the old one.
The design question this card should answer, not dodge
The framing #7654 opened with still stands and is the real deliverable here: what is the single source of truth for skill rows that both the meta HTTP list and the MCP prompt bridge read from? Pointing listSkills at the merged read is the obvious repair, but it is worth stating explicitly whether the bridge should consume getMetaItems (protocol layer, overlay-aware) or whether the override merge belongs lower down in the MetadataService so every consumer of list() gets it — the second is a wider contract change and would subsume this card and part of #6504.
A needs_decision exit naming that fork is a legitimate outcome; answering "just point the bridge at getMetaItems" without addressing it leaves the two paths structurally divergent and merely hides the symptom.
Reproduction
- Boot a showcase with a packaged
skill. PUT /api/v1/meta/skill/{name} with {active:true} — 200.- Query the skill over MCP prompts — the
active:true flip is not reflected.
Source
Symptom 2 of #7654, itself extracted from the QA run #7627 (framework 92f26f7, console 6314e87f). The original observation was recorded as unconfirmed; symptom 1 has since been confirmed and fixed, this half has not been independently root-caused beyond the source divergence above.
Generated by Claude Code
Split out of #7654 (symptom 2) at dispatch time, per the lane ruling recorded there: the metadata half landed in
packages/metadata-protocol, and this half lives inpackages/mcp, which isdomain:cli's lane. Filed unassigned with nodomain:*label so triage routes it.Blocked-by: #7654
The fact
PUT /api/v1/meta/skill/{name}with{active:true}returns 200, and the flip is not reflected over MCP prompts.The two surfaces read different sources:
listSkillscallsmetadataService.list('skill')— the registry/package listing, which carries nosys_metadatastore overrides. Located inpackages/mcp/src/mcp-server-runtime.tsaslistSkills: async () => (await metadataService.list('skill')) ?? [](anchor on the symbol, not the line — the file has moved under several merges).protocol.getMetaItems, which merges the store overrides.So a runtime override lands on the
getMetaItemspath and never on the path the bridge consumes.What #7654 changed, and what it did NOT change
#7654 fixed the metadata half only — the duplicate-row defect on
GET /api/v1/meta/skill. Measured there:getMetaItemsran two different merge implementations that disagreed about a package-less row, and the MetadataService merge now uses the same package-aware resolution as the overlay merge.That fix does not change the bridge's answer at all, which is the question the triage comment on #7654 asked to have settled before this was treated as one bug. It does not, because the bridge never calls
getMetaItems; it callsmetadataService.listdirectly, one layer below where any overlay merging happens. The two symptoms shared a description, not a cause.What #7654 does provide is the thing this card needs: a
getMetaItemsread that now returns exactly one correctly-merged row per skill name. That is why this isBlocked-byrather than independent — the merged read is worth pointing the bridge at only once it is correct, and as of #7654 it is.Coordination — read this before starting
(await metadataService.list('skill')) ?? []as one of its two measured consumers and proposes adding alistDiagnosedcounterpart toIMetadataServiceso a known-partial answer stops presenting as a complete one.Both cards change the same call with different intents, which is the failure mode where each side is individually green and the merged result is wrong. Whoever picks this up should re-read #6504's outcome first: if
listDiagnosedhas landed, express this change against the new method rather than the old one.The design question this card should answer, not dodge
The framing #7654 opened with still stands and is the real deliverable here: what is the single source of truth for skill rows that both the meta HTTP list and the MCP prompt bridge read from? Pointing
listSkillsat the merged read is the obvious repair, but it is worth stating explicitly whether the bridge should consumegetMetaItems(protocol layer, overlay-aware) or whether the override merge belongs lower down in the MetadataService so every consumer oflist()gets it — the second is a wider contract change and would subsume this card and part of #6504.A
needs_decisionexit naming that fork is a legitimate outcome; answering "just point the bridge atgetMetaItems" without addressing it leaves the two paths structurally divergent and merely hides the symptom.Reproduction
skill.PUT /api/v1/meta/skill/{name}with{active:true}— 200.active:trueflip is not reflected.Source
Symptom 2 of #7654, itself extracted from the QA run #7627 (framework 92f26f7, console 6314e87f). The original observation was recorded as unconfirmed; symptom 1 has since been confirmed and fixed, this half has not been independently root-caused beyond the source divergence above.
Generated by Claude Code