garrytan/gstack · error · Error

Skill name is empty.

Error message

Skill name is empty.

What it means

Thrown by validateSkillName when the name is falsy (empty string, undefined coerced to '', or any value where `!name` is true). This is the first guard in the skill-write pipeline (stageSkill and commitSkill both call validateSkillName first), so any /skillify flow or programmatic caller that fails to supply a name hits this before any filesystem touch.

Source

Thrown at browse/src/browser-skill-write.ts:37

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { mkdirSecure } from './file-permissions';
import { isPathWithin } from './platform';
import type { TierPaths } from './browser-skills';
import { defaultTierPaths } from './browser-skills';

// ─── Naming validation ──────────────────────────────────────────

/**
 * Skill names must be safe directory names: lowercase letters, digits, dashes.
 * Starts with a letter, no consecutive dashes, no trailing dash, ≤64 chars.
 * Rejects '..', leading dots, slashes, anything that could escape the tier dir.
 */
const SKILL_NAME_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;

export function validateSkillName(name: string): void {
  if (!name) throw new Error('Skill name is empty.');
  if (name.length > 64) throw new Error(`Skill name too long (${name.length} > 64).`);
  if (!SKILL_NAME_PATTERN.test(name)) {
    throw new Error(
      `Invalid skill name "${name}". Must be lowercase letters/digits/dashes, ` +
      `start with a letter, no leading/trailing/consecutive dashes.`,
    );
  }
}

// ─── Staging ────────────────────────────────────────────────────

export interface StageSkillOptions {
  name: string;
  /** Map of relative path → contents. Path may contain '/' for nested dirs. */
  files: Map<string, string | Buffer>;
  /** Optional override (tests pass synthetic spawn ids). */
  spawnId?: string;
  /** Optional override (tests pass a fake tmp root). */

View on GitHub (pinned to 94993f7401)

Solutions

  1. Supply a non-empty name matching the SKILL_NAME_PATTERN.
  2. Derive the name from the skill's host or purpose before calling stageSkill/commitSkill.
  3. If the name comes from agent output, validate and re-prompt when empty.

Example fix

// before
stageSkill({ name: '', files: ... });
// after
stageSkill({ name: 'hn-frontpage', files: ... });
Defensive patterns

Strategy: validation

Validate before calling

function requireNonEmptyName(name: unknown): asserts name is string {
  if (typeof name !== 'string' || name.length === 0) {
    throw new Error('Skill name is empty.');
  }
}
// before stageSkill/commitSkill:
requireNonEmptyName(opts.name);

Type guard

const isNonEmptyString = (x: unknown): x is string =>
  typeof x === 'string' && x.length > 0;

Prevention

When it happens

Trigger: validateSkillName(''), validateSkillName(undefined as any), or stageSkill({name: '', ...}) / commitSkill({name: '', ...}). Also reachable by directly importing and calling validateSkillName from a test or downstream tool.

Common situations: Agent's /skillify flow derived the name from a heading that came back empty; a templating step stripped the name; programmatic caller passed undefined where a name was expected; a wrapper script consumed the name from an env var that was unset.

Related errors


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