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
- Run `$B skill list` to confirm the exact name and tier.
- Check the project tier is being detected: run inside a git repo so detectProjectRoot resolves, or pass ctx.tiers explicitly.
- Inspect ~/.gstack/browser-skills/<name>/SKILL.md and the bundled dir for frontmatter validity (must have `name` and `host`).
- 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
- Cache `$B skill list` output and validate names against it before show/run/test.
- When running outside a project, expect the project tier to be null — only global/bundled are visible.
- Inspect <tier>/.tombstones/ if a previously-working skill suddenly 404s.
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
- Skill "${name}" not found.
- Skill "${opts.skill.name}" missing script.ts at ${scriptPath
- Skill "${name}" has no script.test.ts at ${testFile}
- Invalid file path in stageSkill: "${relPath}".
- commitSkill: tier "${opts.tier}" has no resolved path.
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/57070e1f250a53fe.
Report an issue: GitHub.