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.channelView on GitHub (pinned to b70486b9bd)
Solutions
- Wait and retry if GitHub rate-limited the request; set GITHUB_TOKEN to raise limits.
- If behind a proxy, ensure it forwards api.github.com responses unchanged.
- 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
- Set GITHUB_TOKEN to reduce the chance of rate-limit-shaped responses.
- Have callers fall back to the 'next' channel when stable tag fetch fails.
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
- Could not resolve stable tag for '${moduleCode}' (${error.me
- resolveChannel: pinned channel requires a pin value
- resolveChannel: unknown channel '${channel}'
- Failed to clone ${parsed.cloneUrl}${refSuffix}: ${error_.mes
- Tag '${planEntry.pin}' not found in ${parsed.owner}/${parsed
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/b9247f855a85b68b.
Report an issue: GitHub.