garrytan/gstack · error · Error
Cannot delete: no skill for ${normalized} in ${scope} scope.
Error message
Cannot delete: no skill for ${normalized} in ${scope} scope.
Cause: skill does not exist or is already tombstoned.
Action: $B domain-skill list to see what exists. What it means
Thrown by deleteSkill() when resolveLatest(rows) has no entry for the key '<scope>::<normalized_host>'. This means the skill does not exist or was already tombstoned by a previous delete. deleteSkill appends a tombstone row, so double-deleting is an error.
Source
Thrown at browse/src/domain-skills.ts:425
export async function listSkills(projectSlug: string): Promise<{ project: DomainSkillRow[]; global: DomainSkillRow[] }> {
const projectRows = await readRows(projectFile(projectSlug));
const globalRows = await readRows(globalFile());
const projectLatest = Array.from(resolveLatest(projectRows).values());
const globalLatest = Array.from(resolveLatest(globalRows).values()).filter((r) => r.state === 'global');
return { project: projectLatest, global: globalLatest };
}
/**
* Tombstone a skill. Append a tombstone row; compactor cleans up later.
*/
export async function deleteSkill(host: string, projectSlug: string, scope: SkillScope = 'project'): Promise<void> {
const normalized = normalizeHost(host);
const file = scope === 'project' ? projectFile(projectSlug) : globalFile();
const rows = await readRows(file);
const latest = resolveLatest(rows);
const current = latest.get(`${scope}::${normalized}`);
if (!current) {
throw new Error(
`Cannot delete: no skill for ${normalized} in ${scope} scope.\n` +
'Cause: skill does not exist or is already tombstoned.\n' +
'Action: $B domain-skill list to see what exists.'
);
}
const tombstone: DomainSkillRow = {
...current,
version: current.version + 1,
updated_ts: new Date().toISOString(),
tombstone: true,
};
await appendRow(file, tombstone);
}
View on GitHub (pinned to 94993f7401)
Solutions
- Run '$B domain-skill list' to verify the skill still exists in the target scope
- Check the scope argument — the skill may be in 'project' scope while you passed 'global' or vice versa
- Verify host name spelling and normalization
Example fix
// before — deleting from wrong scope
await deleteSkill('example.com', 'my-proj', 'global'); // skill is in project scope
// after — correct scope
await deleteSkill('example.com', 'my-proj', 'project'); Defensive patterns
Strategy: validation
Validate before calling
// Check skill exists in target scope before deleting
const rows = await readRows(scope === 'project' ? projectFile(projectSlug) : globalFile());
const latest = resolveLatest(rows);
const key = `${scope}::${normalizeHost(host)}`;
if (!latest.has(key)) {
console.error(`No skill for ${host} in ${scope} scope. Already deleted or never existed.`);
return;
}
await deleteSkill(host, projectSlug, scope); Type guard
function skillExistsInScope(latest: Map<string, DomainSkillRow>, host: string, scope: SkillScope): boolean {
return latest.has(`${scope}::${normalizeHost(host)}`);
} Prevention
- Track deletion state in your application to avoid double-delete attempts
- Verify the scope parameter matches where the skill actually lives (project vs global)
- Call '$B domain-skill list' before delete to confirm existence
When it happens
Trigger: Calling deleteSkill(host, projectSlug, scope) when no non-tombstoned row exists for the host in that scope.
Common situations: Attempt to delete a skill that was already deleted (double-delete), wrong scope parameter (trying 'global' when skill is 'project'), or host name mismatch after normalization.
Related errors
- Cannot promote: no skill for ${normalized} in project ${proj
- Save blocked: classifier flagged content as potential inject
- Cannot promote: skill for ${normalized} is in state "${curre
- 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/23ae5dad57b65869.
Report an issue: GitHub.