{"record":{"id":"555abcd82634dee4","repo":"garrytan/gstack","slug":"section-id-no-section-id-in-skill","errorCode":null,"errorMessage":"{{SECTION:${id}}} — no section \"${id}\" in ${skill}/sections/manifest.json","messagePattern":"(.+?)\\} — no section \"(.+?)\" in (.+?)/sections/manifest\\.json","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/resolvers/sections.ts","lineNumber":46,"sourceCode":"  file: string;\n  title: string;\n  trigger: string;\n}\ninterface SectionManifest {\n  skill: string;\n  sections: SectionEntry[];\n}\n\nfunction loadManifest(skill: string): SectionManifest {\n  const p = path.join(ROOT, skill, 'sections', 'manifest.json');\n  const raw = fs.readFileSync(p, 'utf-8');\n  return JSON.parse(raw) as SectionManifest;\n}\n\nfunction findSection(skill: string, id: string): SectionEntry {\n  const entry = loadManifest(skill).sections.find(s => s.id === id);\n  if (!entry) {\n    throw new Error(`{{SECTION:${id}}} — no section \"${id}\" in ${skill}/sections/manifest.json`);\n  }\n  return entry;\n}\n\n/**\n * {{SECTION:id}} — pointer on Claude, inline on other hosts.\n * Claude path uses the stable gstack-root install (`{skillRoot}/{skill}/sections/`),\n * which always exists, instead of a naked relative path (Codex outside-voice #7).\n */\nexport const SECTION: ResolverFn = (ctx: TemplateContext, args?: string[]): string => {\n  const id = args?.[0];\n  if (!id) throw new Error('{{SECTION:id}} requires a section id');\n  const entry = findSection(ctx.skillName, id);\n\n  if (ctx.host === 'claude') {\n    const sectionPath = `${ctx.paths.skillRoot}/${ctx.skillName}/sections/${entry.file}`;\n    return [\n      `> **STOP.** Before ${entry.trigger}, Read \\`${sectionPath}\\` and execute it`,","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/scripts/resolvers/sections.ts#L28-L64","documentation":"findSection() in scripts/resolvers/sections.ts:46 looks up the id in the skill's sections/manifest.json. If no entry matches it throws, naming the missing id and the manifest path. The SECTION resolver delegates to findSection to locate the entry's file and trigger.","triggerScenarios":"Referencing {{SECTION:foo}} before adding it to manifest.json. Renaming a section id without updating references. Typo in the id (e.g. onbording vs onboarding).","commonSituations":"Splitting a monolithic skill into sections. Copying a section reference from another skill. Manifest out of sync with template references.","solutions":["Add the id to sections/manifest.json with a `file` and `trigger` field","Fix the typo to match an existing manifest id","Regenerate the manifest if it is built by a separate step"],"exampleFix":"<!-- before -->\n{{SECTION:onbording}}\n<!-- after -->\n{{SECTION:onboarding}}","handlingStrategy":"type-guard","validationCode":"import { readFileSync } from 'fs';\nconst ids = new Set(JSON.parse(readFileSync(`${skill}/sections/manifest.json`, 'utf-8')).sections.map((s: any) => s.id));\nif (!ids.has(id)) {\n  throw new Error(`section id ${id} not in ${skill}/sections/manifest.json`);\n}","typeGuard":"const isSectionId = (skill: string, id: string): boolean =>\n  loadManifest(skill).sections.some(s => s.id === id);","tryCatchPattern":null,"preventionTips":["Grep section ids across .tmpl files when renaming one","Keep manifest.json entries documented and sorted","Add new sections to manifest.json before referencing them"],"tags":["template","resolver","sections","manifest"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}