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

  1. Add the id to sections/manifest.json with a `file` and `trigger` field
  2. Fix the typo to match an existing manifest id
  3. 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

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


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/555abcd82634dee4. Report an issue: GitHub.