nexu-io/open-design · error

automation proposal not found

Error message

automation proposal not found

What it means

Thrown by updateProposalStatus() when the given id is not present in the persisted proposals list. updateProposalStatus is called internally after apply/reject handlers have already fetched the proposal, so reaching this branch usually means the proposal was removed between the get and the update (a race or concurrent delete).

Source

Thrown at apps/daemon/src/automation-proposals.ts:286

  }
  const patch = parseJsonPatchAfter(proposal);
  const template = await upsertUserAutomationTemplate(dataDir, patch);
  return {
    automationTemplateId: template.id,
    action: proposal.action,
    path: `automation-templates/${template.id}.json`,
  };
}

async function updateProposalStatus(
  dataDir: string,
  id: string,
  status: AutomationProposalStatus,
  metadataPatch: Record<string, unknown> = {},
): Promise<AutomationEvolutionProposal> {
  const proposals = await listAutomationProposals(dataDir, { status: 'all' });
  const index = proposals.findIndex((proposal) => proposal.id === id);
  if (index < 0) throw new Error('automation proposal not found');
  const current = proposals[index]!;
  const next: AutomationEvolutionProposal = {
    ...current,
    status,
    updatedAt: new Date().toISOString(),
  };
  if (Object.keys(metadataPatch).length > 0) {
    const currentMetadata =
      current.metadata && typeof current.metadata === 'object' && !Array.isArray(current.metadata)
        ? current.metadata as Record<string, unknown>
        : {};
    next.metadata = {
      ...currentMetadata,
      ...metadataPatch,
    } as NonNullable<AutomationEvolutionProposal['metadata']>;
  } else if (current.metadata !== undefined) {
    next.metadata = current.metadata as NonNullable<AutomationEvolutionProposal['metadata']>;
  }

View on GitHub (pinned to 5be4028344)

Solutions

  1. Re-fetch the proposal and confirm it still exists before retrying.
  2. Serialize reviewer actions through a single queue to avoid the get/update race.
  3. Avoid editing proposals.json out-of-band while the daemon is serving apply/reject requests.
Defensive patterns

Strategy: try-catch

Validate before calling

const proposals = await listAutomationProposals(dataDir, { status: 'all' });
if (!proposals.some((p) => p.id === id)) {
  throw new Error('automation proposal not found');
}

Try / catch

try {
  await updateProposalStatus(dataDir, id, status, patch);
} catch (e) {
  if ((e as Error).message === 'automation proposal not found') {
    // re-fetch; the proposal was removed mid-flight — do not retry blindly
  }
}

Prevention

When it happens

Trigger: Two concurrent callers: one applies/rejects and the other deletes the proposal, then the first caller's updateProposalStatus runs and cannot find the id. Also reachable if the proposals.json file was edited externally and the id no longer exists.

Common situations: Concurrent reviewer actions; external script rewriting proposals.json; proposal expired/rotated between fetch and status update; test reset that wiped the store mid-flow.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/e8d25e77401bbec3. Report an issue: GitHub.