garrytan/gstack · error · Error
Cannot promote: skill for ${normalized} is in state "${curre
Error message
Cannot promote: skill for ${normalized} is in state "${current.state}", expected "active".
Cause: skill must be active in this project (used ${PROMOTE_THRESHOLD}+ times without flag) before global promotion.
Action: use the skill in this project until it auto-promotes to active. What it means
Thrown by promoteToGlobal() when the skill exists but current.state !== 'active'. Skills start in 'quarantined' state and must earn PROMOTE_THRESHOLD or more uses without being flagged before they become 'active' and are eligible for global promotion. This enforces a trust gradient — only proven skills cross the project-to-global boundary.
Source
Thrown at browse/src/domain-skills.ts:355
/**
* Promote an active per-project skill to global. Explicit operator call only —
* never auto-promoted across project boundaries (T4).
*/
export async function promoteToGlobal(host: string, projectSlug: string): Promise<DomainSkillRow> {
const normalized = normalizeHost(host);
const rows = await readRows(projectFile(projectSlug));
const latest = resolveLatest(rows);
const current = latest.get(`project::${normalized}`);
if (!current) {
throw new Error(
`Cannot promote: no skill for ${normalized} in project ${projectSlug}.\n` +
'Cause: skill does not exist or is tombstoned.\n' +
'Action: $B domain-skill list to see what exists in this project.'
);
}
if (current.state !== 'active') {
throw new Error(
`Cannot promote: skill for ${normalized} is in state "${current.state}", expected "active".\n` +
`Cause: skill must be active in this project (used ${PROMOTE_THRESHOLD}+ times without flag) before global promotion.\n` +
'Action: use the skill in this project until it auto-promotes to active.'
);
}
const now = new Date().toISOString();
const globalRow: DomainSkillRow = {
...current,
scope: 'global',
state: 'global',
version: 1, // global file has its own version line
use_count: 0,
flag_count: 0,
updated_ts: now,
};
await appendRow(globalFile(), globalRow);
return globalRow;
}View on GitHub (pinned to 94993f7401)
Solutions
- Use the skill in the project repeatedly (PROMOTE_THRESHOLD+ times) without it being flagged — it will auto-promote to 'active'
- Check current state with '$B domain-skill list' to see if it is still 'quarantined'
- Ensure no flags are being raised against the skill during use, as flags reset or block promotion
Defensive patterns
Strategy: validation
Validate before calling
// Check state before promoting
const rows = await readRows(projectFile(projectSlug));
const latest = resolveLatest(rows);
const current = latest.get(`project::${normalizeHost(host)}`);
if (!current || current.state !== 'active') {
console.error(`Skill is in state '${current?.state ?? 'missing'}'. Use it more to auto-promote.`);
return;
}
await promoteToGlobal(host, projectSlug); Type guard
function isSkillActive(row: DomainSkillRow | undefined): row is DomainSkillRow {
return row !== undefined && row.state === 'active';
} Prevention
- Track skill usage count and state in your application before attempting promotion
- Do not attempt promotion on freshly created (quarantined) skills — let them accumulate clean uses first
- Monitor for flags against the skill, as flags block the auto-promotion to active
When it happens
Trigger: Calling promoteToGlobal() on a skill whose latest row has state 'quarantined' (or any state other than 'active').
Common situations: A newly created skill that has not yet accumulated enough clean uses. The user tries to promote it to global scope prematurely, before the auto-promotion threshold is reached.
Related errors
- Cannot promote: no skill for ${normalized} in project ${proj
- Save blocked: classifier flagged content as potential inject
- Cannot rollback: ${normalized} has fewer than 2 versions in
- Cannot delete: no skill for ${normalized} in ${scope} scope.
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/6324b909e52167a6.
Report an issue: GitHub.