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

Unexpected response from ${url}

Error message

Unexpected response from ${url}

What it means

Thrown (as TypeError) by fetchStableTags when the GitHub tags API response is not a JSON array. The resolver expects an array of tag objects; a non-array indicates the endpoint returned an unexpected shape (e.g. an error object, a rate-limit message, or an HTML page).

Source

Thrown at tools/installer/modules/channel-resolver.js:112

}

/**
 * Fetch pure-semver tags (highest first) from a GitHub repo.
 * Cached per-process per owner/repo.
 *
 * @returns {Promise<Array<{tag: string, version: string}>>}
 *   tag is the original ref name (e.g. "v1.7.0"), version is the cleaned
 *   semver (e.g. "1.7.0").
 */
async function fetchStableTags(owner, repo, { timeout } = {}) {
  const cacheKey = `${owner}/${repo}`;
  if (tagCache.has(cacheKey)) return tagCache.get(cacheKey);

  // GitHub returns up to 100 tags per page; one page is plenty for our modules.
  const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/tags?per_page=100`;
  const raw = await fetchJson(url, { timeout });
  if (!Array.isArray(raw)) {
    throw new TypeError(`Unexpected response from ${url}`);
  }

  const stable = [];
  for (const entry of raw) {
    const version = normalizeStableTag(entry?.name);
    if (version) stable.push({ tag: entry.name, version });
  }
  stable.sort((a, b) => semver.rcompare(a.version, b.version));

  tagCache.set(cacheKey, stable);
  return stable;
}

/**
 * Resolve a channel plan for a single module into a git-clonable ref.
 *
 * @param {Object} args
 * @param {'stable'|'next'|'pinned'} args.channel

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Wait and retry if GitHub rate-limited the request; set GITHUB_TOKEN to raise limits.
  2. If behind a proxy, ensure it forwards api.github.com responses unchanged.
  3. Switch the module channel to 'next' or 'pinned' to avoid the tags API entirely.
Defensive patterns

Strategy: try-catch

Type guard

function isTagArray(raw) {
  return Array.isArray(raw) && raw.every((e) => e && typeof e.name === 'string');
}

Try / catch

try {
  const tags = await fetchStableTags(owner, repo);
} catch (error) {
  if (error instanceof TypeError && error.message.startsWith('Unexpected response from')) {
    // fall back to 'next' channel or retry after backoff
  }
  throw error;
}

Prevention

When it happens

Trigger: fetchStableTags(owner, repo) calls fetchJson on the GitHub `/repos/{owner}/{repo}/tags` URL and the parsed body is not an Array. Typically a 2xx with a non-list body, or an upstream proxy returning JSON that is not a list.

Common situations: GitHub returns a rate-limit/secondary-rate-limit response with a 200 and a JSON object, a corporate proxy rewrites the response, or the API contract changes.

Related errors


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