bmad-code-org/BMAD-METHOD · error · Error
Local paths do not support @version suffixes
Error message
Local paths do not support @version suffixes
What it means
Thrown by resolveSource() when parseSource() identifies the input as a local path but it carries an @version suffix (e.g., './my-module@1.2.0'). Local paths cannot be versioned because they point at a fixed directory on disk — version resolution only applies to remote Git URLs.
Source
Thrown at tools/installer/modules/custom-module-manager.js:331
if (!Array.isArray(plugins) || plugins.length === 0) {
throw new Error('marketplace.json contains no plugins');
}
return plugins.map((plugin) => this._normalizeCustomModule(plugin, sourceUrl, marketplaceData));
}
// ─── Source Resolution ────────────────────────────────────────────────────
/**
* High-level coordinator: parse input, clone if URL, determine discovery vs direct mode.
* @param {string} input - URL or local path
* @param {Object} [options] - Options passed to cloneRepo
* @returns {Object} { parsed, rootDir, repoPath, sourceUrl, marketplace, mode: 'discovery'|'direct' }
*/
async resolveSource(input, options = {}) {
const parsed = this.parseSource(input);
if (!parsed.isValid) throw new Error(parsed.error);
let rootDir;
let repoPath;
let sourceUrl;
if (parsed.type === 'local') {
rootDir = parsed.localPath;
repoPath = null;
sourceUrl = null;
} else {
repoPath = await this.cloneRepo(input, options);
sourceUrl = parsed.cloneUrl;
rootDir = parsed.subdir ? path.join(repoPath, parsed.subdir) : repoPath;
if (parsed.subdir && !(await fs.pathExists(rootDir))) {
throw new Error(`Subdirectory '${parsed.subdir}' not found in cloned repository`);
}
}View on GitHub (pinned to b70486b9bd)
Solutions
- Remove the @version suffix from the local path — local directories are used as-is.
- If you need versioning, install from a Git URL instead of a local path.
- If the path genuinely contains an '@' in a directory name, rename the directory or use a Git remote.
Example fix
// before
await mgr.resolveSource('./modules/repo@1.2.0');
// after
await mgr.resolveSource('./modules/repo'); Defensive patterns
Strategy: validation
Validate before calling
// Detect local path + version suffix before calling resolveSource
function isLocalWithVersion(input) {
const localPrefixes = ['/', './', '../', '.\\', '..\\', '~'];
const isLocal = localPrefixes.some(p => input.startsWith(p)) || path.win32.isAbsolute(input);
if (!isLocal) return false;
const lastAt = input.lastIndexOf('@');
return lastAt > 0 && /^[\w.\-+/]+$/.test(input.slice(lastAt + 1));
}
if (isLocalWithVersion(input)) {
throw new Error('Remove the @version suffix from local paths');
} Try / catch
try {
await mgr.resolveSource(input);
} catch (e) {
if (e.message === 'Local paths do not support @version suffixes') {
const cleanInput = input.replace(/@[\w.\-+/]+$/, '');
console.log(`Retrying without version suffix: ${cleanInput}`);
await mgr.resolveSource(cleanInput);
return;
}
throw e;
} Prevention
- Strip @version suffixes from local paths before passing to the installer.
- Distinguish between URL and local path inputs in your code before formatting.
- Educate users that version pinning only applies to remote Git URLs.
When it happens
Trigger: Calling resolveSource('./local/dir@v2') or resolveSource('~/modules/repo@main'). The parser strips the @suffix, detects the remainder as a local path (starts with /, ./, ../, ~, or is a Windows absolute path), and returns isValid:false.
Common situations: A user copies a URL install pattern (URL@version) to a local path; a script appends a version to a path programmatically without distinguishing URL vs. local sources.
Related errors
- Path does not exist: ${resolved}
- Unsafe ref name: ${JSON.stringify(ref)}
- Source is required
- Not a valid Git URL or local path
- Unsafe ref name: ${JSON.stringify(ref)}
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/3c72e2ddde282bc4.
Report an issue: GitHub.