{"record":{"id":"e9052011efc746f5","repo":"garrytan/gstack","slug":"skill-name-is-empty","errorCode":null,"errorMessage":"Skill name is empty.","messagePattern":"Skill name is empty\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/browser-skill-write.ts","lineNumber":37,"sourceCode":"import * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { mkdirSecure } from './file-permissions';\nimport { isPathWithin } from './platform';\nimport type { TierPaths } from './browser-skills';\nimport { defaultTierPaths } from './browser-skills';\n\n// ─── Naming validation ──────────────────────────────────────────\n\n/**\n * Skill names must be safe directory names: lowercase letters, digits, dashes.\n * Starts with a letter, no consecutive dashes, no trailing dash, ≤64 chars.\n * Rejects '..', leading dots, slashes, anything that could escape the tier dir.\n */\nconst SKILL_NAME_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;\n\nexport function validateSkillName(name: string): void {\n  if (!name) throw new Error('Skill name is empty.');\n  if (name.length > 64) throw new Error(`Skill name too long (${name.length} > 64).`);\n  if (!SKILL_NAME_PATTERN.test(name)) {\n    throw new Error(\n      `Invalid skill name \"${name}\". Must be lowercase letters/digits/dashes, ` +\n      `start with a letter, no leading/trailing/consecutive dashes.`,\n    );\n  }\n}\n\n// ─── Staging ────────────────────────────────────────────────────\n\nexport interface StageSkillOptions {\n  name: string;\n  /** Map of relative path → contents. Path may contain '/' for nested dirs. */\n  files: Map<string, string | Buffer>;\n  /** Optional override (tests pass synthetic spawn ids). */\n  spawnId?: string;\n  /** Optional override (tests pass a fake tmp root). */","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/browser-skill-write.ts#L19-L55","documentation":"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.","triggerScenarios":"validateSkillName(''), validateSkillName(undefined as any), or stageSkill({name: '', ...}) / commitSkill({name: '', ...}). Also reachable by directly importing and calling validateSkillName from a test or downstream tool.","commonSituations":"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.","solutions":["Supply a non-empty name matching the SKILL_NAME_PATTERN.","Derive the name from the skill's host or purpose before calling stageSkill/commitSkill.","If the name comes from agent output, validate and re-prompt when empty."],"exampleFix":"// before\nstageSkill({ name: '', files: ... });\n// after\nstageSkill({ name: 'hn-frontpage', files: ... });","handlingStrategy":"validation","validationCode":"function requireNonEmptyName(name: unknown): asserts name is string {\n  if (typeof name !== 'string' || name.length === 0) {\n    throw new Error('Skill name is empty.');\n  }\n}\n// before stageSkill/commitSkill:\nrequireNonEmptyName(opts.name);","typeGuard":"const isNonEmptyString = (x: unknown): x is string =>\n  typeof x === 'string' && x.length > 0;","tryCatchPattern":null,"preventionTips":["Derive skill names from deterministic sources (host, purpose) rather than free-form agent text.","Validate at the boundary — the earliest point the name enters your code.","Unit-test the empty-name path in any wrapper around stageSkill/commitSkill."],"tags":["validation","skill-management","gstack"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}