actualbudget/actual · error · Error

There is already a report named ${item.name}

Error message

There is already a report named ${item.name}

What it means

Thrown by createReport when reportNameExists finds an existing non-deleted (tombstone = 0) custom_reports row with the same name. Names act as unique identifiers for custom reports among living reports, so creating a duplicate name is rejected with this message.

Source

Thrown at packages/loot-core/src/server/reports/app.ts:146

  }

  //default return: item was found but does not match current name
  return true;
}

async function createReport(report: CustomReportEntity) {
  const reportId = uuidv4();
  const item: CustomReportEntity = {
    ...report,
    id: reportId,
  };
  if (!item.name) {
    throw new Error('Report name is required');
  }

  const nameExists = await reportNameExists(item.name, item.id ?? '', true);
  if (nameExists) {
    throw new Error('There is already a report named ' + item.name);
  }

  // Create the report here based on the info
  await db.insertWithSchema('custom_reports', reportModel.fromJS(item));

  return reportId;
}

async function updateReport(item: CustomReportEntity) {
  if (!item.name) {
    throw new Error('Report name is required');
  }

  if (!item.id) {
    throw new Error('Report recall error');
  }

  const nameExists = await reportNameExists(item.name, item.id, false);

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pick a unique name — check existing names first via getReports and suffix if taken
  2. Wrap the call and catch the error, then fall back to creating with a suffixed name ('Name (2)')
  3. For re-runnable imports, look up the existing report by name and update it (updateReport) instead of creating
  4. Delete the existing duplicate report if it is stale and recreation is intended

Example fix

// before
await app.createReport({ name: existingName, ...report });
// after
const existing = reports.find(r => r.name === report.name);
if (existing) {
  await app.updateReport({ ...report, id: existing.id });
} else {
  await app.createReport(report);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await app.getReports();
if (existing.some(r => r.name === newReport.name)) {
  newReport.name = `${newReport.name} (${new Date().toISOString().slice(0, 10)})`;
}

Try / catch

try {
  await app.createReport(report);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('There is already a report named ')) {
    await app.createReport({ ...report, name: report.name + ' (2)' });
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling createReport with a name that matches any existing report in the custom_reports table (case-exact match via SQL name = ?), regardless of the new report's id — e.g. re-running an import script, duplicating a report through the API, or retrying a create after a partial failure.

Common situations: Idempotency-lacking automation that re-imports reports on every run; users/API scripts recreating a report that already exists; testing scripts that insert the same sample report repeatedly; renaming one report to collide with another before create.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/97ffffbf5f305b45. Report an issue: GitHub.