actualbudget/actual · error

Parameter "${param.paramName}" can't convert to ${type} (alr

Error message

Parameter "${param.paramName}" can't convert to ${type} (already inferred as ${existingType})

What it means

AQL parameters are typed on first use via inferParam (compiler.ts:222). If the same named parameter is later used where an incompatible type is required (and no automatic cast exists in the casts table, e.g. string->date, date->date-month, integer->float), a plain Error is thrown because the parameter would need contradictory SQL types.

Source

Thrown at packages/loot-core/src/server/aql/compiler.ts:237

  throw new CompileError(`Bad ${type} format: ${str}`);
}

function inferParam(param, type) {
  const existingType = param.paramType;
  if (existingType) {
    const casts = {
      date: ['string'],
      'date-month': ['date'],
      'date-year': ['date', 'date-month'],
      id: ['string'],
      float: ['integer'],
    };

    if (
      existingType !== type &&
      (!casts[type] || !casts[type].includes(existingType))
    ) {
      throw new Error(
        `Parameter "${param.paramName}" can't convert to ${type} (already inferred as ${existingType})`,
      );
    }
  } else {
    param.paramType = type;
  }
}

function castInput(state, expr, type) {
  if (expr.type === type) {
    return expr;
  } else if (expr.type === 'param') {
    inferParam(expr, type);
    return typed(expr.value, type);
  } else if (expr.type === 'null') {
    if (!expr.literal) {
      throw new CompileError("A non-literal null doesn't make sense");
    }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Use a distinct parameter name per logical type, e.g. :startDate for dates and :minAmount for numbers.
  2. If reuse is intended, pass values whose types are compatible with the casts table (string can become date/id; integer can become float; date can become date-month/date-year).
  3. Ensure the first usage of the parameter is the type you intend, since the first context fixes paramType.

Example fix

// before
.filter({ date: { $gte: ':p' } }).filter({ amount: { $gt: ':p' } })
// after
.filter({ date: { $gte: ':startDate' } }).filter({ amount: { $gt: ':minAmount' } })
Defensive patterns

Strategy: validation

Validate before calling

function makeParams(spec) {
  // spec: { startDate: 'date', minAmount: 'float' }
  const used = new Map();
  for (const [name, type] of Object.entries(spec)) {
    if (used.has(name) && used.get(name) !== type) {
      throw new Error(`Param ${name} used with conflicting types`);
    }
    used.set(name, type);
  }
  return spec;
}

Type guard

function paramTypeMatches(param, type) {
  const casts = { date: ['string'], id: ['string'], float: ['integer'], 'date-month': ['date'], 'date-year': ['date', 'date-month'] };
  return param.paramType === type || (casts[type] || []).includes(param.paramType);
}

Try / catch

try {
  runQuery(q, params);
} catch (e) {
  if (e.message.includes("can't convert to")) {
    throw new Error('Use a distinct parameter name for each value type');
  } else throw e;
}

Prevention

When it happens

Trigger: Reusing one parameter in contexts demanding different types: q('transactions').filter({ date: { $eq: ':param' } }).filter({ amount: { $gt: ':param' } }) — first inferred as date, then required as float.

Common situations: Reusing a single placeholder across filters of different columns; generating queries in loops where one param name is shared; building generic query helpers that always use the same param key.

Related errors


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