nexu-io/open-design · error
proposal ${proposal.id} is ${proposal.status}, not reviewabl
Error message
proposal ${proposal.id} is ${proposal.status}, not reviewable What it means
Thrown by assertReviewable() when a proposal's status is neither 'pending-review' nor 'draft'. Only those two states may be applied or rejected; proposals already applied, rejected, superseded, or failed cannot be acted on again. The guard runs in both applyAutomationProposal and rejectAutomationProposal after the proposal is fetched.
Source
Thrown at apps/daemon/src/automation-proposals.ts:123
? input.sourcePacketIds.filter((id): id is string => typeof id === 'string' && id.length > 0)
: [],
...(typeof input.automationRunId === 'string' ? { automationRunId: input.automationRunId } : {}),
...(typeof input.targetRef === 'string' ? { targetRef: input.targetRef } : {}),
patch: input.patch,
...(typeof input.confidence === 'number' ? { confidence: input.confidence } : {}),
...(input.compressionReport ? { compressionReport: input.compressionReport } : {}),
...(input.metadata === undefined ? {} : { metadata: input.metadata }),
};
const proposals = await listAutomationProposals(dataDir, { status: 'all' });
const next = proposals.filter((existing) => existing.id !== proposal.id);
next.push(proposal);
await writeProposals(dataDir, next);
return proposal;
}
function assertReviewable(proposal: AutomationEvolutionProposal): void {
if (proposal.status === 'pending-review' || proposal.status === 'draft') return;
throw new Error(`proposal ${proposal.id} is ${proposal.status}, not reviewable`);
}
function safeMemoryType(value: unknown): MemoryType {
return typeof value === 'string' && VALID_MEMORY_TYPES.has(value as MemoryType)
? (value as MemoryType)
: 'project';
}
function parseJsonPatchAfter(proposal: AutomationEvolutionProposal): Record<string, unknown> {
if (proposal.patch.format !== 'json') return {};
const after = proposal.patch.after;
if (typeof after !== 'string' || !after.trim()) return {};
try {
const parsed = JSON.parse(after);
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
} catch {
throw new Error('proposal patch.after is not valid JSON');
}View on GitHub (pinned to 5be4028344)
Solutions
- Refresh the proposal list and check status before offering apply/reject actions.
- Disable the action in the UI once status leaves 'pending-review'/'draft'.
- Treat this error as a no-op in idempotent callers (log and skip) rather than retrying.
Example fix
// before — unconditional apply
await applyAutomationProposal(dataDir, id);
// after — guard on status
const p = await getAutomationProposal(dataDir, id);
if (p && (p.status === 'pending-review' || p.status === 'draft')) {
await applyAutomationProposal(dataDir, id);
} Defensive patterns
Strategy: validation
Validate before calling
const proposal = await getAutomationProposal(dataDir, id);
if (!proposal) throw new Error('automation proposal not found');
if (proposal.status !== 'pending-review' && proposal.status !== 'draft') {
throw new Error(`proposal ${id} is ${proposal.status}, not reviewable`);
} Type guard
function isReviewable(status: string): boolean {
return status === 'pending-review' || status === 'draft';
} Try / catch
try {
await applyAutomationProposal(dataDir, id);
} catch (e) {
if (/not reviewable/.test((e as Error).message)) {
// already actioned; refresh UI, do not retry
}
} Prevention
- Refresh proposal status before offering apply/reject actions.
- Disable actions once status leaves pending-review/draft.
- Treat 'not reviewable' as a no-op in idempotent callers.
When it happens
Trigger: Calling applyAutomationProposal() or rejectAutomationProposal() on a proposal whose status is 'applied', 'rejected', 'superseded', or 'failed'. Common when a UI action is double-clicked or a stale proposal id is reused.
Common situations: Reviewer double-submits (apply then apply again); two reviewers acting concurrently; client retrying after a timeout when the first call already succeeded; automated loop reprocessing an old proposal list.
Related errors
- proposal body is required
- proposal title is required
- proposal summary is required
- proposal patch is required
- proposal patch.after is not valid JSON
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/b48eaefc9f8f05d0.
Report an issue: GitHub.