nexu-io/open-design · error
automation template requires at least one valid stage
Error message
automation template requires at least one valid stage
What it means
Thrown after the stages array is mapped and filtered. Each stage survives only if it has a valid id (passes SAFE_ID), a non-empty string title, and a kind in STAGE_KINDS (ingest, canonicalize, classify, compress, redact, agent-run, propose per the built-in templates). If zero stages survive, the template is rejected.
Source
Thrown at apps/daemon/src/automation-templates.ts:260
const stageRaw = stage as Record<string, unknown>;
const stageId = cleanId(stageRaw.id);
const kind = stageRaw.kind;
const title = stageRaw.title;
if (!stageId || typeof title !== 'string' || !title.trim()) return null;
if (typeof kind !== 'string' || !STAGE_KINDS.has(kind as AutomationTemplateStageKind)) {
return null;
}
return {
id: stageId,
kind: kind as AutomationTemplateStageKind,
title: title.trim(),
...(typeof stageRaw.description === 'string' && stageRaw.description.trim()
? { description: stageRaw.description.trim() }
: {}),
};
})
.filter((stage): stage is AutomationTemplate['stages'][number] => Boolean(stage));
if (stages.length === 0) throw new Error('automation template requires at least one valid stage');
return {
id,
title: requiredString(raw, 'title'),
description: requiredString(raw, 'description'),
purpose: requiredString(raw, 'purpose'),
triggerKinds: cleanEnumArray(raw.triggerKinds, TRIGGER_KINDS, ['manual']),
sourceKinds: cleanEnumArray(raw.sourceKinds, SOURCE_KINDS, ['chat']),
stages,
outputSinks: cleanEnumArray(raw.outputSinks, OUTPUT_SINKS, ['memory']),
reviewPolicy: cleanReviewPolicy(raw.reviewPolicy),
tokenCompression: cleanCompressionMode(raw.tokenCompression),
...(cleanStringArray(raw.tags).length > 0 ? { tags: cleanStringArray(raw.tags) } : {}),
};
}
async function readUserAutomationTemplates(dataDir: string): Promise<AutomationTemplate[]> {
let parsed: unknown;
try {View on GitHub (pinned to 5be4028344)
Solutions
- Include at least one stage with a valid id, a non-empty title, and a kind from STAGE_KINDS.
- Verify each kind against the current enum (ingest, canonicalize, classify, compress, redact, agent-run, propose) — note the kebab-case spelling.
- If your stages array legitimately could be empty, reconsider whether a template is the right shape; the model requires at least one stage.
Example fix
// before
await upsertUserAutomationTemplate(dataDir, {
id: 'my-template', title: 'T', description: 'D', purpose: 'P',
stages: [{ id: 'ingest', kind: 'INGEST', title: 'Capture' }], // unknown kind -> filtered out -> throws
});
// after
await upsertUserAutomationTemplate(dataDir, {
id: 'my-template', title: 'T', description: 'D', purpose: 'P',
stages: [{ id: 'ingest', kind: 'ingest', title: 'Capture' }],
}); Defensive patterns
Strategy: validation
Validate before calling
const SAFE_ID = /^[a-z0-9][a-z0-9._-]{1,95}$/;
const STAGE_KINDS = new Set(['ingest', 'canonicalize', 'classify', 'compress', 'redact', 'agent-run', 'propose']);
function validStages(stages: unknown): boolean {
if (!Array.isArray(stages) || stages.length === 0) return false;
return stages.every(s =>
s && typeof s === 'object' && !Array.isArray(s)
&& typeof (s as any).id === 'string' && SAFE_ID.test((s as any).id)
&& typeof (s as any).title === 'string' && (s as any).title.trim().length > 0
&& typeof (s as any).kind === 'string' && STAGE_KINDS.has((s as any).kind));
}
if (!validStages(payload.stages)) throw new Error('At least one valid stage is required.'); Type guard
const STAGE_KINDS = new Set(['ingest', 'canonicalize', 'classify', 'compress', 'redact', 'agent-run', 'propose']);
function isValidStage(s: unknown): s is { id: string; kind: string; title: string } {
if (!s || typeof s !== 'object' || Array.isArray(s)) return false;
const o = s as Record<string, unknown>;
return typeof o.id === 'string' && /^[a-z0-9][a-z0-9._-]{1,95}$/.test(o.id)
&& typeof o.title === 'string' && o.title.trim().length > 0
&& typeof o.kind === 'string' && STAGE_KINDS.has(o.kind);
} Try / catch
try {
await upsertUserAutomationTemplate(dataDir, payload);
} catch (err) {
if (err instanceof Error && err.message === 'automation template requires at least one valid stage') {
return badRequest('Include at least one stage with id, title, and a known kind (ingest, canonicalize, classify, compress, redact, agent-run, propose).');
}
throw err;
} Prevention
- Always include at least one fully-formed stage.
- Use kebab-case kinds spelled exactly as in STAGE_KINDS (note 'agent-run', not 'agent_run').
- Validate stage kind spelling against the current enum before submitting — kinds can change across versions.
When it happens
Trigger: Passing stages: []; passing stages whose kind is unknown or misspelled (e.g. 'agent_run' vs 'agent-run', or uppercased 'INGEST'); stages missing title or with an invalid id; stages that are not plain objects.
Common situations: Renaming a stage kind in the payload that no longer matches the enum; copy-pasting a stage and forgetting to update its kind; version drift where an older template used a kind that has since been removed.
Related errors
- automation template ${key} is required
- automation template must be an object
- automation template id must be a safe slug
- sourceKind must be one of upload, url, repo, connector, arti
- ingestion body is required
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/f9a93c01f0a1ee7c.
Report an issue: GitHub.