garrytan/gstack · error · Error

Skill "${name}" not found in any tier.

Error message

Skill "${name}" not found in any tier.

What it means

Thrown by handleShow after readBrowserSkill walked all three tiers (project > global > bundled) and found no skill directory whose SKILL.md exists and parses cleanly. readBrowserSkill returns null on miss OR on a malformed SKILL.md (parse failure is swallowed and the next tier is tried), so this error can also mask a skill whose SKILL.md is invalid in every tier where the name exists.

Source

Thrown at browse/src/browser-skill-commands.ts:115

      [
        s.name.padEnd(30),
        s.tier.padEnd(8),
        s.frontmatter.host.padEnd(28),
        desc,
      ].join(' '),
    );
  }
  return lines.join('\n') + '\n';
}

// ─── show ───────────────────────────────────────────────────────

function handleShow(args: string[], ctx: SkillCommandContext): string {
  const name = args[0];
  if (!name) throw new Error('Usage: $B skill show <name>');
  const tiers = ctx.tiers ?? defaultTierPaths();
  const skill = readBrowserSkill(name, tiers);
  if (!skill) throw new Error(`Skill "${name}" not found in any tier.`);
  return readFile(path.join(skill.dir, 'SKILL.md'));
}

function readFile(p: string): string {
  return fs.readFileSync(p, 'utf-8');
}

// ─── run ────────────────────────────────────────────────────────

interface ParsedRunArgs {
  passthrough: string[];
  timeoutSeconds: number;
}

export function parseSkillRunArgs(args: string[]): ParsedRunArgs {
  const passthrough: string[] = [];
  let timeoutSeconds = DEFAULT_TIMEOUT_SECONDS;
  for (let i = 0; i < args.length; i++) {

View on GitHub (pinned to 94993f7401)

Solutions

  1. Run `$B skill list` to confirm the exact name and tier.
  2. Check the project tier is being detected: run inside a git repo so detectProjectRoot resolves, or pass ctx.tiers explicitly.
  3. Inspect ~/.gstack/browser-skills/<name>/SKILL.md and the bundled dir for frontmatter validity (must have `name` and `host`).
  4. If tombstoned, look under <tier>/.tombstones/<name>-<ts>/ and move it back.
Defensive patterns

Strategy: validation

Validate before calling

import { listBrowserSkills, defaultTierPaths } from './browser-skills';

function ensureSkillExists(name: string, tiers = defaultTierPaths()): void {
  const known = new Set(listBrowserSkills(tiers).map(s => s.name));
  if (!known.has(name)) {
    throw new Error(`Skill "${name}" not found. Available: ${[...known].join(', ') || '(none)'}`);
  }
}
// before handleShow:
ensureSkillExists(name);

Type guard

import type { BrowserSkill } from './browser-skills';
const isBrowserSkill = (x: unknown): x is BrowserSkill =>
  typeof x === 'object' && x !== null && typeof (x as any).name === 'string' && typeof (x as any).dir === 'string';

Prevention

When it happens

Trigger: handleShow with a name that does not match any directory under <project>/.gstack/browser-skills/, ~/.gstack/browser-skills/, or <install>/browser-skills/. Also triggered when the only matching directory lacks a SKILL.md or has one that fails parseSkillFile (missing required `host` field, unparseable frontmatter).

Common situations: Typo in the skill name; skill was tombstoned (moved to .tombstones/); running outside a project so the project tier is null and only global/bundled are visible; SKILL.md was hand-edited and now missing the required `host` field; bundled install path shifted after a gstack upgrade.

Related errors


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