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

Could not resolve stable tag for '${moduleCode}' (${error.me

Error message

Could not resolve stable tag for '${moduleCode}' (${error.message}). ${hint}

What it means

Thrown by cloneExternalModule() when resolveChannel() throws (GitHub API rate limit or network failure) AND there is no cached clone to fall back to. The error includes the underlying failure message and a contextual hint about setting GITHUB_TOKEN or using --next/--pin to bypass the tag lookup.

Source

Thrown at tools/installer/modules/external-manager.js:328

          channel: cachedMarker.channel,
          version: cachedMarker.version || 'main',
          ref: cachedMarker.version && cachedMarker.version !== 'main' ? cachedMarker.version : null,
          sha: cachedMarker.sha,
          repoUrl: moduleInfo.url,
          resolvedFallback: false,
          planSource: 'cached',
        });
        return moduleCacheDir;
      }
      // No cache to fall back on — this is effectively a fresh install with
      // no offline safety net. Surface a clear error with actionable guidance.
      const isRateLimited = /rate limit/i.test(error.message);
      const hint = isRateLimited
        ? process.env.GITHUB_TOKEN
          ? 'Your GITHUB_TOKEN may have expired or been rate-limited on its own budget. Try a different token or wait for the reset.'
          : 'Set a GITHUB_TOKEN env var (any personal access token with public-repo read) to raise the 60-req/hour anonymous limit.'
        : `Check your network connection, or rerun with \`--next=${moduleCode}\` / \`--pin ${moduleCode}=<tag>\` to skip the tag lookup.`;
      throw new Error(`Could not resolve stable tag for '${moduleCode}' (${error.message}). ${hint}`);
    }

    if (resolved.resolvedFallback && !silent) {
      if (resolved.reason === 'no-stable-tags') {
        await prompts.log.warn(`No stable releases found for ${moduleInfo.name}; installing from main.`);
      } else if (resolved.reason === 'not-a-github-url') {
        await prompts.log.warn(`Cannot determine stable tags for ${moduleInfo.name} (non-GitHub URL); installing from main.`);
      }
    }

    // Validate pin before we burn time cloning. Best-effort: skip on non-GitHub URLs.
    if (planEntry.channel === 'pinned') {
      const parsed = parseGitHubRepo(moduleInfo.url);
      if (parsed) {
        try {
          const exists = await tagExists(parsed.owner, parsed.repo, planEntry.pin);
          if (!exists) {
            throw new Error(`Tag '${planEntry.pin}' not found in ${parsed.owner}/${parsed.repo}.`);

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Set a GITHUB_TOKEN environment variable (any personal access token with public-repo read) to raise the rate limit to 5000 req/hr.
  2. If already using a token, verify it is valid and not rate-limited on its own budget.
  3. Rerun with --next=<moduleCode> to skip the stable tag lookup and clone from main.
  4. Rerun with --pin <moduleCode>=<tag> to specify an exact tag and skip the API lookup.
  5. Check network connectivity to api.github.com.

Example fix

# before (anonymous, rate limited)
npx bmad-method install

# after (with token)
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
npx bmad-method install

# or bypass tag lookup
npx bmad-method install --next=my-module
Defensive patterns

Strategy: retry

Validate before calling

// Check rate limit status before fresh install
function hasGithubToken() {
  return !!process.env.GITHUB_TOKEN;
}

// For CI/automated installs, always set a token
if (!hasGithubToken() && process.env.CI) {
  console.warn('No GITHUB_TOKEN set — anonymous rate limit (60/hr) may be hit.');
}

// Use --pin or --next to bypass the tag API entirely
const options = {
  channelOptions: {
    pins: new Map([['module-code', 'v1.0.0']]), // skips tag lookup
  },
};

Try / catch

try {
  await extMgr.cloneExternalModule(moduleCode, options);
} catch (e) {
  if (e.message.includes('Could not resolve stable tag') && e.message.includes('rate limit')) {
    // Retry with --next to skip tag API
    console.log('Rate limited. Retrying with --next to bypass tag lookup...');
    await extMgr.cloneExternalModule(moduleCode, {
      ...options,
      channelOptions: { nextSet: new Set([moduleCode]) },
    });
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Fresh install of an external module (no cache) when the GitHub tags API returns 403 (rate limited, 60 req/hr anonymous limit) or the network is unreachable. If a cached clone existed with a channel marker, the code falls back to it instead of throwing.

Common situations: CI environment without GITHUB_TOKEN that hits the 60-req/hr anonymous rate limit; corporate firewall blocks api.github.com; GITHUB_TOKEN expired or revoked; DNS failure; the user is installing many modules in quick succession without a token.

Related errors


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