{"record":{"id":"dfca112c8837dde7","repo":"garrytan/gstack","slug":"section-id-requires-a-section-id","errorCode":null,"errorMessage":"{{SECTION:id}} requires a section id","messagePattern":"(.+?)\\} requires a section id","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/resolvers/sections.ts","lineNumber":58,"sourceCode":"  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`,\n      `> in full. Do not work from memory — that section is the source of truth for this step.`,\n    ].join('\\n');\n  }\n\n  // Non-Claude hosts inline the section template content (monolith preserved).\n  // Inner {{RESOLVER}} tokens are expanded by the generator's multi-pass resolve.\n  const tmplPath = path.join(ROOT, ctx.skillName, 'sections', `${entry.file}.tmpl`);\n  return fs.readFileSync(tmplPath, 'utf-8').trimEnd();\n};\n\n/**\n * {{SECTION_INDEX:skill}} — situation→section table from the passive manifest.","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/scripts/resolvers/sections.ts#L40-L76","documentation":"The SECTION resolver in scripts/resolvers/sections.ts:58 requires at least one colon argument (the section id). Thrown when {{SECTION}} or {{SECTION:}} appears in a template. After validation it delegates to findSection and emits a pointer on Claude or inlines content on other hosts.","triggerScenarios":"Typing {{SECTION}} without an id. Trimming a placeholder and dropping the argument. Misremembering the placeholder syntax.","commonSituations":"Hand-editing a .tmpl and forgetting the argument. Copy-paste from docs that omitted the id.","solutions":["Provide the id: `{{SECTION:onboarding}}`"],"exampleFix":"<!-- before -->\n{{SECTION}}\n<!-- after -->\n{{SECTION:onboarding}}","handlingStrategy":"validation","validationCode":"if (/\\{\\{SECTION:?\\}\\}/.test(tmplContent)) {\n  console.error(`{{SECTION}} requires an id in ${relTmplPath}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pair SECTION with a colon and id","Lint .tmpl files for empty-argument placeholders","Copy the canonical form {{SECTION:onboarding}} from working skills"],"tags":["template","resolver","sections"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}