{"record":{"id":"5dba4e2b1700e4d2","repo":"garrytan/gstack","slug":"unresolved-placeholders-in-reltmplpath-remai","errorCode":null,"errorMessage":"Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}","messagePattern":"Unresolved placeholders in (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/gen-skill-docs.ts","lineNumber":710,"sourceCode":"      if (appliesTo && !appliesTo(ctx)) return '';\n      return args.length > 0 ? resolve(ctx, args) : resolve(ctx);\n    });\n\n  // Multi-pass: a resolver may emit content that itself contains {{TOKENS}} — the\n  // {{SECTION:id}} resolver inlines a section template (with its own resolvers)\n  // for non-Claude hosts. .replace() doesn't re-scan inserted text, so loop until\n  // the output stabilizes. Bounded to avoid an infinite loop if a resolver ever\n  // emits its own placeholder; 6 passes is far more nesting than any skill needs.\n  let content = tmplContent;\n  for (let pass = 0; pass < 6; pass++) {\n    const next = onePass(content);\n    if (next === content) break;\n    content = next;\n  }\n\n  const remaining = content.match(/\\{\\{(\\w+(?::[^}]+)?)\\}\\}/g);\n  if (remaining) {\n    throw new Error(`Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}`);\n  }\n  return content;\n}\n\n/**\n * Build the TemplateContext from a template's frontmatter. Shared by SKILL.md\n * and section generation so sections inherit the SAME context the parent skill\n * resolves with (skillName, tier, benefitsFrom, interactive) — enforced by\n * test/template-context-parity.test.ts. skillNameOverride lets section\n * generation pin the parent skill's name instead of deriving \"sections\".\n */\nfunction buildContext(\n  tmplContent: string,\n  tmplPath: string,\n  host: Host,\n  skillNameOverride?: string,\n): TemplateContext {\n  const { name: extractedName } = extractNameAndDescription(tmplContent);","sourceCodeStart":692,"sourceCodeEnd":728,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/scripts/gen-skill-docs.ts#L692-L728","documentation":"After the bounded 6-pass resolution loop in scripts/gen-skill-docs.ts:710, the generator scans for surviving {{...}} tokens. If any remain it throws listing them. This catches nested-section placeholders whose inner resolvers are missing, cycles, or a resolver that emits its own placeholder.","triggerScenarios":"A section template references {{UNKNOWN}} that the parent context cannot resolve. A resolver emits text containing a {{SELF}} placeholder. Deeply nested {{SECTION:...}} chains exceeding 6 levels.","commonSituations":"Adding a new section that uses a placeholder not present in the parent resolver context. Resolver bug that re-emits placeholder syntax. Section content referencing legacy tokens.","solutions":["Inspect each token in `remaining` against the RESOLVERS map","For nested sections, ensure the inner template resolves against the same context (see buildContext / template-context-parity.test.ts)","Fix the resolver so its output is placeholder-free","Only as a last resort, raise the 6-pass loop bound if a legitimate >6-deep nesting exists"],"exampleFix":"<!-- before: section.tmpl references a token no resolver provides -->\n{{LEGACY_TOKEN}}\n<!-- after: replace with a literal or a known resolver -->\n{{INVOKE_SKILL:plan-ceo-review}}","handlingStrategy":"validation","validationCode":"const after = resolvePasses(tmplContent, 6);\nconst leftovers = after.match(/\\{\\{\\w+(?::[^}]+)?\\}\\}/g);\nif (leftovers) {\n  console.error(`Unresolved placeholders: ${leftovers.join(', ')}`);\n  process.exit(2);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Audit new section templates for tokens not provided by the parent context","Test that resolver output is placeholder-free before nesting","Keep section nesting shallow; the bound is 6 passes by design"],"tags":["template","resolver","build","nested"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}