bmad-code-org/BMAD-METHOD · error · TypeError

Path must be a string.

Error message

Path must be a string.

What it means

Thrown by expandUserPath (as TypeError) when inputPath is not a string. The method does inputPath.trim() and tilde expansion, both of which require a string; non-string input is rejected at the top before any path logic runs.

Source

Thrown at tools/installer/ui.js:1659

      // Stop at root
      const parent = path.dirname(currentPath);
      if (await fs.pathExists(parent)) {
        return parent;
      }
      currentPath = parent;
    }

    return null; // No existing parent found (shouldn't happen in practice)
  }

  /**
   * Expands the user-provided path: handles ~ and resolves to absolute.
   * @param {string} inputPath - User input path.
   * @returns {string} Absolute expanded path.
   */
  expandUserPath(inputPath) {
    if (typeof inputPath !== 'string') {
      throw new TypeError('Path must be a string.');
    }

    let expanded = inputPath.trim();

    // Handle tilde expansion
    if (expanded.startsWith('~')) {
      if (expanded === '~') {
        expanded = os.homedir();
      } else if (expanded.startsWith('~' + path.sep)) {
        const pathAfterHome = expanded.slice(2); // Remove ~/ or ~\
        expanded = path.join(os.homedir(), pathAfterHome);
      } else {
        const restOfPath = expanded.slice(1);
        const separatorIndex = restOfPath.indexOf(path.sep);
        const username = separatorIndex === -1 ? restOfPath : restOfPath.slice(0, separatorIndex);
        if (username) {
          throw new Error(`Path expansion for ~${username} is not supported. Please use an absolute path or ~${path.sep}`);
        }

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Ensure the value passed is a string before calling expandUserPath.
  2. Guard callers: if (typeof dir === 'string') expandUserPath(dir).
  3. Default options.directory to undefined and skip the directory branch when unset, rather than forwarding undefined.

Example fix

// before
//   const dir = this.expandUserPath(options.directory); // options.directory may be undefined
//
// after
//   if (typeof options.directory !== 'string') {
//     throw new Error('options.directory must be a string');
//   }
//   const dir = this.expandUserPath(options.directory);
Defensive patterns

Strategy: type-guard

Validate before calling

if (options.directory != null && typeof options.directory !== 'string') {
  throw new TypeError('options.directory must be a string');
}
if (typeof options.directory === 'string') {
  const dir = ui.expandUserPath(options.directory);
}

Type guard

function isStringPath(p) { return typeof p === 'string'; }

Try / catch

try {
  dir = ui.expandUserPath(options.directory);
} catch (e) {
  if (/Path must be a string/.test(e.message)) {
    if (typeof options.directory !== 'string') options.directory = undefined;
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling expandUserPath(undefined), expandUserPath(null), or passing a number/object. Indirectly when options.directory is truthy but not a string and is forwarded here.

Common situations: Programmatic calls passing undefined; a CLI option parsed in a way that yields a non-string; config loading returning undefined that isn't defaulted.

Related errors


AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13). Data as JSON: /api/errors/bf05557c43f72575. Report an issue: GitHub.