{"record":{"id":"41285d27d199cec3","repo":"garrytan/gstack","slug":"stageskill-files-map-is-empty","errorCode":null,"errorMessage":"stageSkill: files map is empty.","messagePattern":"stageSkill: files map is empty\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/browser-skill-write.ts","lineNumber":70,"sourceCode":"  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). */\n  tmpRoot?: string;\n}\n\n/**\n * Stage a skill into the staging tree:\n *   <tmpRoot>/.gstack/.tmp/skillify-<spawnId>/<name>/\n *\n * The leaf <name> directory is what gets renamed during commit. The wrapper\n * skillify-<spawnId>/ is per-spawn so concurrent /skillify invocations don't\n * collide. Returns the absolute path to the staged skill dir (ending in <name>).\n */\nexport function stageSkill(opts: StageSkillOptions): string {\n  validateSkillName(opts.name);\n  if (opts.files.size === 0) {\n    throw new Error('stageSkill: files map is empty.');\n  }\n\n  const spawnId = opts.spawnId ?? generateSpawnId();\n  const tmpRoot = opts.tmpRoot ?? path.join(os.homedir(), '.gstack', '.tmp');\n  const wrapperDir = path.join(tmpRoot, `skillify-${spawnId}`);\n  const stagedDir = path.join(wrapperDir, opts.name);\n\n  mkdirSecure(wrapperDir);\n  mkdirSecure(stagedDir);\n\n  for (const [relPath, contents] of opts.files) {\n    if (relPath.startsWith('/') || relPath.includes('..')) {\n      // Defense in depth: validateSkillName above bounds the leaf, but a\n      // bad relPath in files could still write outside the staged dir.\n      throw new Error(`Invalid file path in stageSkill: \"${relPath}\".`);\n    }\n    const filePath = path.join(stagedDir, relPath);\n    const fileDir = path.dirname(filePath);","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/browser-skill-write.ts#L52-L88","documentation":"Thrown by stageSkill after validateSkillName passed but opts.files.size === 0. stageSkill is the entry point of /skillify's write path; an empty files map means there is nothing to write (no SKILL.md, no script.ts), so it bails before creating the per-spawn wrapper dir under ~/.gstack/.tmp/.","triggerScenarios":"stageSkill({name: 'foo', files: new Map()}); or a caller that built the files map conditionally and skipped every entry. Common in agent flows that gate file production on scraped content that came back empty.","commonSituations":"Agent's /skillify flow produced zero files because the scrape returned nothing; a wrapper filtered out every file via an allowlist; map constructor was called without entries by mistake; programmatic caller forgot to populate files.","solutions":["Ensure at least SKILL.md and script.ts are in the files map before calling stageSkill.","Gating logic that filters files should fail loudly when it would produce an empty map rather than passing it downstream.","Add a pre-check: `if (files.size === 0) throw new Error('no files generated');` at the source."],"exampleFix":"// before\nstageSkill({ name: 'foo', files: new Map() });\n// after\nconst files = new Map([['SKILL.md', '---\\nname: foo\\nhost: example.com\\n---'], ['script.ts', '...']]);\nstageSkill({ name: 'foo', files });","handlingStrategy":"validation","validationCode":"function assertFilesNonEmpty(files: Map<string, string | Buffer>): void {\n  if (files.size === 0) {\n    throw new Error('stageSkill: files map is empty. Generate at least SKILL.md + script.ts.');\n  }\n}\n// before stageSkill:\nassertFilesNonEmpty(opts.files);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Fail loud at the source if a scrape/template produced no files.","Ensure SKILL.md and script.ts are always populated before calling stageSkill.","Add a unit test that stageSkill throws on an empty map."],"tags":["validation","skill-management","gstack"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}