hcengineering/platform · error

Variable ${matched[0]} not found

Error message

Variable ${matched[0]} not found

What it means

fillValue substitutes variables of the form matched by fieldRegexp (e.g. {{name}}-style placeholders) from a vars lookup map. When a placeholder is found in the string but has no entry in vars, the value cannot be resolved, so it throws naming the missing variable. The loop repeats until no placeholders remain, so any unresolved variable aborts the fill.

Source

Thrown at server/tool/src/initializer.ts:386

      if (Array.isArray(value)) {
        return await Promise.all(value.map(async (v) => await this.fillValue(v, vars)))
      } else {
        return await this.fillProps(value, vars)
      }
    } else if (typeof value === 'string') {
      if (value === this.nextRank) {
        const rank = makeRank(vars[this.nextRank], undefined)
        vars[this.nextRank] = rank
        return rank
      } else if (value === this.now) {
        return new Date().getTime()
      } else {
        while (true) {
          const matched = fieldRegexp.exec(value)
          if (matched === null) break
          const result = vars[matched[0]]
          if (result === undefined) {
            throw new Error(`Variable ${matched[0]} not found`)
          } else {
            value = value.replaceAll(matched[0], result)
            fieldRegexp.lastIndex = 0
          }
        }
        return value
      }
    }
    return value
  }
}

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Add the missing variable to the vars map with the exact key the regexp matches (including delimiters if matched[0] includes them)
  2. Print the vars object and compare keys against the placeholders in the value
  3. Fix the template typo in the property value

Example fix

// before
const value = 'Hello {{customerName}}' // vars has only { name: 'John' }
// after
const vars = { ...baseVars, '{{customerName}}': customer.name } // key must match matched[0]
// or fix the template to use an existing variable: 'Hello {{name}}'
Defensive patterns

Strategy: validation

Validate before calling

function findUnresolvedVars(value: string, vars: Record<string, string>, regexp: RegExp): string[] {
  const missing: string[] = []
  for (const m of value.match(new RegExp(regexp.source, regexp.flags.replace('g', '') + 'g')) ?? []) {
    if (vars[m] === undefined) missing.push(m)
  }
  return missing
}
const missing = findUnresolvedVars(template, vars, fieldRegexp)
if (missing.length > 0) throw new Error(`Unresolved variables: ${missing.join(', ')}`)

Try / catch

try {
  filled = await fillValue(value, vars)
} catch (err) {
  if (err instanceof Error && err.message.includes('not found')) {
    const varName = err.message.match(/Variable (.+) not found/)?.[1]
    console.error(`Add '${varName}' to vars or fix the template`, { available: Object.keys(vars) })
  }
  throw err
}

Prevention

When it happens

Trigger: fillProps/fillValue encountering a placeholder in a property value whose key (matched[0], including delimiters) is absent from the vars map — e.g. variable defined with a different key spelling, or vars not populated for this step.

Common situations: Migration templates referencing a variable that was never registered; variables keyed without the regexp delimiters in the map while the lookup uses matched[0] (with delimiters); renamed variables in templates without updating the vars source (config/account data).

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/10e433467371ad26. Report an issue: GitHub.