garrytan/gstack · error · Error
Invalid preamble-tier: ${tier} in ${ctx.tmplPath}. Must be 1
Error message
Invalid preamble-tier: ${tier} in ${ctx.tmplPath}. Must be 1-4. What it means
generatePreamble() in scripts/resolvers/preamble.ts:82 reads ctx.preambleTier (default 4). The tier selects which preamble sections render (T1 minimal through T4 full). Values outside 1-4 throw, naming ctx.tmplPath so the offending template is identifiable.
Source
Thrown at scripts/resolvers/preamble.ts:82
// Standalone export used directly by the resolver registry
export { generateTestFailureTriage } from './preamble/generate-test-failure-triage';
// Preamble Composition (tier → sections)
// ─────────────────────────────────────────────
// T1: core + upgrade + lake + telemetry + voice(trimmed) + completion
// T2: T1 + voice(full) + ask + completeness + context-recovery + confusion + checkpoint + context-health
// T3: T2 + repo-mode + search
// T4: (same as T3 — TEST_FAILURE_TRIAGE is a separate {{}} placeholder, not preamble)
//
// Skills by tier:
// T1: browse, setup-cookies, benchmark
// T2: investigate, cso, retro, doc-release, setup-deploy, canary, context-save, context-restore, health
// T3: autoplan, codex, design-consult, office-hours, ceo/design/eng-review
// T4: ship, review, qa, qa-only, design-review, land-deploy
export function generatePreamble(ctx: TemplateContext): string {
const tier = ctx.preambleTier ?? 4;
if (tier < 1 || tier > 4) {
throw new Error(`Invalid preamble-tier: ${tier} in ${ctx.tmplPath}. Must be 1-4.`);
}
const sections = [
generatePreambleBash(ctx),
...(ctx.skillName === 'make-pdf' ? [generateMakePdfSetup(ctx)] : []),
// Plan-mode-skill semantics stays near the top: after bash (so _SESSION_ID /
// _BRANCH / _TEL env vars are live) and before all onboarding gates so
// models read the authoritative "AskUserQuestion satisfies plan mode's
// end-of-turn" rule before any other instruction. Renders for all skills
// (not interactive-gated); the text applies universally.
generatePlanModeInfo(ctx),
generateUpgradeCheck(ctx),
generateWritingStyleMigration(ctx),
generateLakeIntro(),
generateTelemetryPrompt(ctx),
generateProactivePrompt(ctx),
generateFirstRunGuidance(ctx),
generateRoutingInjection(ctx),
generateVendoringDeprecation(ctx),View on GitHub (pinned to 94993f7401)
Solutions
- Set preambleTier to 1, 2, 3, or 4 based on the skill's complexity (see the tier table in the comments)
- Omit the field to default to 4
- If you genuinely need a new tier, extend the bounds and the section assembly in generatePreamble first
Example fix
// before preambleTier: 5 // after preambleTier: 4
Defensive patterns
Strategy: type-guard
Validate before calling
if (ctx.preambleTier != null && (ctx.preambleTier < 1 || ctx.preambleTier > 4)) {
throw new Error(`preambleTier out of range: ${ctx.preambleTier}`);
} Type guard
const isPreambleTier = (n: number): n is 1 | 2 | 3 | 4 => Number.isInteger(n) && n >= 1 && n <= 4;
Prevention
- Use the documented tier-per-skill table in the comments when assigning
- Default to tier 4 unless the skill explicitly warrants less
- Range-check the value in the context builder, not just in the resolver
When it happens
Trigger: Setting `preambleTier: 0` or `preambleTier: 5` in a .tmpl frontmatter. A context-builder bug feeding a non-tier number. Off-by-one when introducing a new tier.
Common situations: Misunderstanding the tier scale. Editing tier per the comments but using the wrong integer. Copying tier from a skill that uses a different default.
Related errors
- {{LEARNINGS_SEARCH:query=...}} value must match ${QUERY_SAFE
- ${hostConfig.displayName} description for "${name}" is ${des
- Unknown placeholder {{${resolverName}}} in ${relTmplPath}
- Unresolved placeholders in ${relTmplPath}: ${remaining.join(
- {{INVOKE_SKILL}} requires a skill name, e.g. {{INVOKE_SKILL:
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/bf386bbd8ddfddd5.
Report an issue: GitHub.