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

resolveChannel: pinned channel requires a pin value

Error message

resolveChannel: pinned channel requires a pin value

What it means

Thrown by resolveChannel when channel === 'pinned' but no pin value was supplied. The pinned channel requires an explicit tag/ref to clone; without one the resolver cannot pick a ref.

Source

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

 *
 * @param {Object} args
 * @param {'stable'|'next'|'pinned'} args.channel
 * @param {string} [args.pin] - Required when channel === 'pinned'
 * @param {string} args.repoUrl - Module's git URL (for tag lookup)
 * @returns {Promise<{channel, ref, version}>} where
 *   ref:     the git ref to pass to `git clone --branch`, or null for HEAD (next)
 *   version: the resolved version string (tag name for stable/pinned, 'main' for next)
 *
 * Throws on:
 *   - pinned without a pin value
 *   - stable with no GitHub repo parseable from the URL (pass through to caller to fall back)
 *
 * Falls back to next-channel semantics and sets resolvedFallback=true when
 * stable resolution turns up no tags.
 */
async function resolveChannel({ channel, pin, repoUrl, timeout }) {
  if (channel === 'pinned') {
    if (!pin) throw new Error('resolveChannel: pinned channel requires a pin value');
    return { channel: 'pinned', ref: pin, version: pin, resolvedFallback: false };
  }

  if (channel === 'next') {
    return { channel: 'next', ref: null, version: 'main', resolvedFallback: false };
  }

  if (channel === 'stable') {
    const parsed = parseGitHubRepo(repoUrl);
    if (!parsed) {
      // No GitHub URL — caller must handle by falling back to next.
      return { channel: 'next', ref: null, version: 'main', resolvedFallback: true, reason: 'not-a-github-url' };
    }

    try {
      const tags = await fetchStableTags(parsed.owner, parsed.repo, { timeout });
      if (tags.length === 0) {
        return { channel: 'next', ref: null, version: 'main', resolvedFallback: true, reason: 'no-stable-tags' };

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Provide a pin tag: pass `--pin v1.2.3` (or set pin in the channel plan).
  2. Switch to the 'stable' or 'next' channel if you do not need a specific tag.
  3. Validate the pin is non-empty before calling resolveChannel.

Example fix

// before
await resolveChannel({ channel: 'pinned', repoUrl });

// after
await resolveChannel({ channel: 'pinned', pin: 'v1.2.3', repoUrl });
Defensive patterns

Strategy: validation

Validate before calling

if (channel === 'pinned' && !pin) {
  throw new Error('The pinned channel requires --pin <tag>.');
}

Type guard

function isValidChannelPlan({ channel, pin }) {
  if (channel === 'pinned') return typeof pin === 'string' && pin.trim().length > 0;
  return ['stable', 'next'].includes(channel);
}

Try / catch

try {
  await resolveChannel({ channel: 'pinned', pin, repoUrl });
} catch (error) {
  if (error.message === 'resolveChannel: pinned channel requires a pin value') { /* prompt for pin */ }
  throw error;
}

Prevention

When it happens

Trigger: resolveChannel({ channel: 'pinned', pin: undefined|null|'' , repoUrl }) — a caller (e.g. external-manager.js or ui.js) constructed a pinned channel plan without a pin.

Common situations: A module config/CLI flag selects the pinned channel but the user did not pass `--pin <tag>`, or a programmatic caller forgot to set the pin field.

Related errors


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