garrytan/gstack · error · Error
{{SECTION:${id}}} — no section "${id}" in ${skill}/sections/
Error message
{{SECTION:${id}}} — no section "${id}" in ${skill}/sections/manifest.json What it means
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.
Source
Thrown at scripts/resolvers/sections.ts:46
file: string;
title: string;
trigger: string;
}
interface SectionManifest {
skill: string;
sections: SectionEntry[];
}
function loadManifest(skill: string): SectionManifest {
const p = path.join(ROOT, skill, 'sections', 'manifest.json');
const raw = fs.readFileSync(p, 'utf-8');
return JSON.parse(raw) as SectionManifest;
}
function findSection(skill: string, id: string): SectionEntry {
const entry = loadManifest(skill).sections.find(s => s.id === id);
if (!entry) {
throw new Error(`{{SECTION:${id}}} — no section "${id}" in ${skill}/sections/manifest.json`);
}
return entry;
}
/**
* {{SECTION:id}} — pointer on Claude, inline on other hosts.
* Claude path uses the stable gstack-root install (`{skillRoot}/{skill}/sections/`),
* which always exists, instead of a naked relative path (Codex outside-voice #7).
*/
export const SECTION: ResolverFn = (ctx: TemplateContext, args?: string[]): string => {
const id = args?.[0];
if (!id) throw new Error('{{SECTION:id}} requires a section id');
const entry = findSection(ctx.skillName, id);
if (ctx.host === 'claude') {
const sectionPath = `${ctx.paths.skillRoot}/${ctx.skillName}/sections/${entry.file}`;
return [
`> **STOP.** Before ${entry.trigger}, Read \`${sectionPath}\` and execute it`,View on GitHub (pinned to 94993f7401)
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
Example fix
<!-- before -->
{{SECTION:onbording}}
<!-- after -->
{{SECTION:onboarding}} Defensive patterns
Strategy: type-guard
Validate before calling
import { readFileSync } from 'fs';
const ids = new Set(JSON.parse(readFileSync(`${skill}/sections/manifest.json`, 'utf-8')).sections.map((s: any) => s.id));
if (!ids.has(id)) {
throw new Error(`section id ${id} not in ${skill}/sections/manifest.json`);
} Type guard
const isSectionId = (skill: string, id: string): boolean => loadManifest(skill).sections.some(s => s.id === id);
Prevention
- 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
When it happens
Trigger: 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).
Common situations: Splitting a monolithic skill into sections. Copying a section reference from another skill. Manifest out of sync with template references.
Related errors
- {{SECTION:id}} requires a section id
- Unknown placeholder {{${resolverName}}} in ${relTmplPath}
- Unresolved placeholders in ${relTmplPath}: ${remaining.join(
- {{INVOKE_SKILL}} requires a skill name, e.g. {{INVOKE_SKILL:
- {{LEARNINGS_SEARCH:query=...}} value must match ${QUERY_SAFE
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/555abcd82634dee4.
Report an issue: GitHub.