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

  1. Run '$B domain-skill list' to verify the skill still exists in the target scope
  2. Check the scope argument — the skill may be in 'project' scope while you passed 'global' or vice versa
  3. 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

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


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/23ae5dad57b65869. Report an issue: GitHub.