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

  1. Use the skill in the project repeatedly (PROMOTE_THRESHOLD+ times) without it being flagged — it will auto-promote to 'active'
  2. Check current state with '$B domain-skill list' to see if it is still 'quarantined'
  3. 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

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


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