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 line

View on GitHub (pinned to 94993f7401)

Solutions

  1. Run '$B domain-skill list' to verify which skills exist in the current project
  2. Check the host spelling — normalizeHost may transform the name (e.g., lowercasing, stripping protocol)
  3. 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

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


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