nexu-io/open-design · error
proposal patch.after markdown is required
Error message
proposal patch.after markdown is required
What it means
Thrown by proposalAfterMarkdown() when proposal.patch.after is not a non-empty string. This helper backs applyDesignSystemProposal and applySkillProposal, both of which write the after value verbatim as DESIGN.md / SKILL.md content. An empty markdown body would create an empty file, so it is rejected.
Source
Thrown at apps/daemon/src/automation-proposals.ts:231
return proposal.metadata && typeof proposal.metadata === 'object' && !Array.isArray(proposal.metadata)
? proposal.metadata as Record<string, unknown>
: {};
}
function targetSlugFor(proposal: AutomationEvolutionProposal, kind: 'design-system' | 'skill'): string {
const expected = kind === 'design-system'
? /^design-systems\/([^/]+)\/DESIGN\.md$/
: /^skills\/([^/]+)\/SKILL\.md$/;
const fromRef = typeof proposal.targetRef === 'string' ? expected.exec(proposal.targetRef)?.[1] : '';
if (fromRef && SAFE_SLUG.test(fromRef)) return fromRef;
const metadata = metadataRecord(proposal);
if (typeof metadata.slug === 'string' && SAFE_SLUG.test(metadata.slug)) return metadata.slug;
return slugifyTarget(proposal.title);
}
function proposalAfterMarkdown(proposal: AutomationEvolutionProposal): string {
if (typeof proposal.patch.after !== 'string' || !proposal.patch.after.trim()) {
throw new Error('proposal patch.after markdown is required');
}
return proposal.patch.after.trimEnd() + '\n';
}
async function applyDesignSystemProposal(dataDir: string, proposal: AutomationEvolutionProposal) {
const slug = targetSlugFor(proposal, 'design-system');
const dir = path.join(dataDir, 'design-systems', slug);
const file = path.join(dir, 'DESIGN.md');
if (proposal.action === 'delete') {
await fsp.rm(dir, { recursive: true, force: true });
return { designSystemId: slug, action: 'delete', path: `design-systems/${slug}/DESIGN.md` };
}
await fsp.mkdir(dir, { recursive: true });
await fsp.writeFile(file, proposalAfterMarkdown(proposal), 'utf8');
return { designSystemId: slug, action: proposal.action, path: `design-systems/${slug}/DESIGN.md` };
}
async function applySkillProposal(dataDir: string, proposal: AutomationEvolutionProposal) {View on GitHub (pinned to 5be4028344)
Solutions
- Set patch.after to the full markdown body for DESIGN.md / SKILL.md before applying.
- Reject the proposal if no real content exists yet.
- Mirror the markdown shape produced by buildDesignSystemProposalMarkdown / buildSkillProposalMarkdown in automation-ingestions.ts.
Example fix
// before
{ targetKind: 'skill', action: 'create', patch: { format: 'markdown', after: '' } }
// after
{ targetKind: 'skill', action: 'create', patch: { format: 'markdown', after: '# My Skill\n\n...body...' } } Defensive patterns
Strategy: validation
Validate before calling
if (typeof proposal.patch.after !== 'string' || !proposal.patch.after.trim()) {
throw new Error('proposal patch.after markdown is required');
} Type guard
function hasMarkdownAfter(patch: unknown): patch is { after: string } {
return !!patch && typeof (patch as any).after === 'string' && (patch as any).after.trim().length > 0;
} Prevention
- Build markdown patches with buildDesignSystemProposalMarkdown / buildSkillProposalMarkdown.
- Reject proposals that have placeholder/empty after content instead of applying.
- Validate after.trim() before persisting skill/design-system proposals.
When it happens
Trigger: Applying a design-system or skill create/update proposal whose patch.format is 'markdown' but patch.after is missing, empty, whitespace-only, or not a string. (For these target kinds, format is expected to be 'markdown'.)
Common situations: Agent created the proposal with a placeholder after it intended to fill later; patch.after was cleared during an edit; proposal built from a template whose body field was empty.
Related errors
- proposal body is required
- proposal title is required
- proposal summary is required
- proposal patch is required
- proposal ${proposal.id} is ${proposal.status}, not reviewabl
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/7bac77d37bbe5d47.
Report an issue: GitHub.