{"record":{"id":"70f469f4765d3438","repo":"phacility/phabricator","slug":"no-such-variable-s","errorCode":null,"errorMessage":"No such variable '%s'!","messagePattern":"No such variable '(.+?)'!","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php","lineNumber":204,"sourceCode":"   * ...into a string with variables merged into it safely:\n   *\n   *   ls 'dir with spaces'\n   *\n   * @param string Name of a `vxsprintf` function, like @{function:vcsprintf}.\n   * @param string User-provided pattern string containing `${variables}`.\n   * @param dict   List of available replacement variables.\n   * @return string String with variables replaced safely into it.\n   */\n  protected function mergeVariables($function, $pattern, array $variables) {\n    $regexp = '@\\\\$\\\\{(?P<name>[a-z\\\\./_-]+)\\\\}@';\n\n    $matches = null;\n    preg_match_all($regexp, $pattern, $matches);\n\n    $argv = array();\n    foreach ($matches['name'] as $name) {\n      if (!array_key_exists($name, $variables)) {\n        throw new Exception(pht(\"No such variable '%s'!\", $name));\n      }\n      $argv[] = $variables[$name];\n    }\n\n    $pattern = str_replace('%', '%%', $pattern);\n    $pattern = preg_replace($regexp, '%s', $pattern);\n\n    return call_user_func($function, $pattern, $argv);\n  }\n\n  public function getFieldSpecifications() {\n    return array();\n  }\n\n  protected function formatSettingForDescription($key, $default = null) {\n    return $this->formatValueForDescription($this->getSetting($key, $default));\n  }\n","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php#L186-L222","documentation":"HarbormasterBuildStepImplementation::mergeVariables() scans a pattern for ${name} placeholders and requires every name to be present in the variables dict before substituting values (it replaces placeholders with %s and calls $function, e.g. vsprintf). An unknown placeholder throws so the sprintf-style call can never receive a mismatched argument list or silently produce broken output.","triggerScenarios":"A command or field pattern containing ${buildable.revision} while the variables array passed to mergeVariables only provides other keys; renaming a variable key while old step configurations still reference the old name.","commonSituations":"Editing a build plan template to reference a variable the current build context does not provide; Phabricator upgrades that change available variable names; copying a step configuration between plans with different variable sets.","solutions":["Correct the pattern so it only references variables that exist in the variables array (e.g. ${revision} not ${buildable.revision})","Extend the variables array passed to mergeVariables to include the missing key","Before editing templates, list the variables the implementation actually provides (shown in the plan editor help) and keep edits consistent with them"],"exampleFix":"// before\n$pattern = 'make ${buildable.revision}'; // 'buildable.revision' not in $variables\n$this->mergeVariables('vsprintf', $pattern, $variables);\n// after\n$pattern = 'make ${revision}';\n$this->mergeVariables('vsprintf', $pattern, $variables);","handlingStrategy":"validation","validationCode":"preg_match_all('/[$][{]([a-z./_-]+)[}]/', $pattern, $m);\nforeach ($m[1] as $name) {\n  if (!array_key_exists($name, $variables)) {\n    // fix the pattern or add the variable before calling mergeVariables\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep step templates and the variables map in the same change when editing plans","Test edited plans with a dry-run build before relying on them"],"tags":["harbormaster","phabricator","build-step","template-variable","substitution"],"backgroundTag":"undefined-template-variable","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}