garrytan/gstack · error · Error
Cannot promote: no skill for ${normalized} in project ${proj
Error message
Cannot promote: no skill for ${normalized} in project ${projectSlug}.
Cause: skill does not exist or is tombstoned.
Action: $B domain-skill list to see what exists in this project. What it means
Thrown by promoteToGlobal() when resolveLatest(rows) returns no entry for the key 'project::<normalized_host>'. This means no active or quarantined skill row exists for the given host in the specified project — the skill was never created, was tombstoned by a prior delete, or the host name does not match after normalization.
Source
Thrown at browse/src/domain-skills.ts:348
flag_count: flagCount,
version: current.version + 1,
updated_ts: new Date().toISOString(),
};
await appendRow(projectFile(projectSlug), updated);
return updated;
}
/**
* 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 lineView on GitHub (pinned to 94993f7401)
Solutions
- Run '$B domain-skill list' to verify which skills exist in the current project
- Check the host spelling — normalizeHost may transform the name (e.g., lowercasing, stripping protocol)
- If the skill was never created, call writeSkill() first, then use it until active before promoting
Example fix
// before — promoting a non-existent skill
await promoteToGlobal('exampl.com', 'my-proj'); // typo
// after — verify then promote
const skills = await listSkills('my-proj');
if (skills.has('example.com')) {
await promoteToGlobal('example.com', 'my-proj');
} Defensive patterns
Strategy: validation
Validate before calling
// Before promoting, check the skill exists in project scope
const rows = await readRows(projectFile(projectSlug));
const latest = resolveLatest(rows);
const key = `project::${normalizeHost(host)}`;
if (!latest.has(key)) {
console.error(`No skill for ${host} in ${projectSlug}. Run '$B domain-skill list'.`);
return;
}
await promoteToGlobal(host, projectSlug); Type guard
function skillExistsInProject(latest: Map<string, DomainSkillRow>, host: string): boolean {
return latest.has(`project::${normalizeHost(host)}`);
} Prevention
- Call '$B domain-skill list' before attempting promotion to verify the skill exists
- Normalize the host name the same way the library does before looking it up
- Confirm the correct projectSlug — skills are scoped per project
When it happens
Trigger: Calling promoteToGlobal(host, projectSlug) where readRows(projectFile(projectSlug)) contains no non-tombstoned row for the normalized host.
Common situations: Typo or casing mismatch in the host name, skill was deleted before promotion attempt, skill was saved to a different project slug, or the skill exists only in global scope.
Related errors
- Cannot promote: skill for ${normalized} is in state "${curre
- Cannot delete: no skill for ${normalized} in ${scope} scope.
- Save blocked: classifier flagged content as potential inject
- Cannot rollback: ${normalized} has fewer than 2 versions in
- State not found: ${statePath}
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/496edaa397136d5c.
Report an issue: GitHub.