mastra-ai/mastra · error · Error

${describeBadPlaceholder(template, idx, rawExpr)} must be of

Error message

${describeBadPlaceholder(template, idx, rawExpr)} must be of the form ${stepResults.<stepId>} or ${stepResults.<stepId>.<path>}.

What it means

For placeholders scoped to stepResults, the expression must contain a step ID: ${stepResults.<stepId>} or ${stepResults.<stepId>.<path>}. If there is no step ID (e.g. "${stepResults}" or "${stepResults.}"), the template is invalid and validateTemplate throws.

Source

Thrown at packages/core/src/workflows/mapping-template.ts:68

 * the value a primitive) stay in {@link resolveTemplate}.
 */
export function validateTemplate(template: string): void {
  let idx = 0;
  for (const match of template.matchAll(TEMPLATE_PLACEHOLDER)) {
    idx++;
    const rawExpr = match[1] ?? '';
    if (rawExpr.length === 0 || rawExpr !== rawExpr.trim()) {
      throw new Error(
        `${describeBadPlaceholder(template, idx, rawExpr)} has empty or whitespace-padded contents. ` +
          `Use \${<scope>.<path>} with no surrounding whitespace.`,
      );
    }
    const { scope, rest } = parseTemplatePlaceholder(rawExpr);
    if (scope === 'stepResults') {
      const innerDot = rest.indexOf('.');
      const stepId = innerDot === -1 ? rest : rest.slice(0, innerDot);
      if (!stepId) {
        throw new Error(
          `${describeBadPlaceholder(template, idx, rawExpr)} must be of the form \${stepResults.<stepId>} or \${stepResults.<stepId>.<path>}.`,
        );
      }
      continue;
    }
    if (scope === 'requestContext') {
      if (!rest) {
        throw new Error(
          `${describeBadPlaceholder(template, idx, rawExpr)} requires a request-context key — use \${requestContext.<key>}.`,
        );
      }
      continue;
    }
    if ((TEMPLATE_NAMESPACES as readonly string[]).includes(scope)) continue;
    throw new Error(
      `${describeBadPlaceholder(template, idx, rawExpr)} references unknown namespace "${scope}". ` +
        `Use one of: ${TEMPLATE_NAMESPACES.join(', ')}.`,
    );

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Add the step ID: "${stepResults.myStep}" or "${stepResults.myStep.output.field}".
  2. Fix accidental double dots or trailing dots in the expression.
  3. Add a validateTemplate() unit test over all mapping constants to catch this before runtime.

Example fix

// before
mapVariable({ value: '${stepResults.}' });
// after
mapVariable({ value: '${stepResults.fetchUser.name}' });
Defensive patterns

Strategy: validation

Validate before calling

const tpl = '${stepResults.fetchUser.name}';
for (const m of tpl.matchAll(/\$\{([^}]*)\}/g)) {
  const [scope, ...rest] = m[1].split('.');
  if (scope === 'stepResults' && (!rest[0] || !rest[0].trim())) {
    throw new Error(`Placeholder '${m[0]}' is missing a step ID`);
  }
}

Try / catch

try {
  validateTemplate(tpl);
} catch (err) {
  if (err instanceof Error && err.message.includes('stepResults.<stepId>')) {
    console.error(`Template '${tpl}' needs a step ID after stepResults`);
  } else throw err;
}

Prevention

When it happens

Trigger: Templates like "${stepResults}" or "${stepResults..path}" where the step ID portion before the first dot is empty.

Common situations: Forgetting the step ID entirely; double dots after refactoring; autocompletion inserting just the scope name.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/16fd608a0a06f564. Report an issue: GitHub.