agalwood/Motrix · error · AppError

PLUGIN_MANIFEST_INVALID

PLUGIN_MANIFEST_INVALID

Error message

plugin.install.invalid_github_spec

What it means

parseGithubSpec requires the form owner/repo or owner/repo@tag (regex ^([^/@\s]+)/([^/@\s]+)(?:@(.+))?$). A spec with whitespace, a missing slash, a leading @, extra path segments, or an unstripped 'github:' prefix does not match and throws PLUGIN_MANIFEST_INVALID.

Source

Thrown at src/core/plugin/install/github-fetcher.ts:21

//
// We don't read the manifest here — that happens later in `extractMoext`.
// We just provide a clean tarball-style "bring me the bytes" boundary.

import { mkdir, rm, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { AppError, ErrorCode } from '@shared/errors'
import { Agent, interceptors, request } from 'undici'

export interface GhReleaseSpec {
  owner: string
  repo: string
  tag?: string
}

export function parseGithubSpec(spec: string): GhReleaseSpec {
  const m = spec.match(/^([^/@\s]+)\/([^/@\s]+)(?:@(.+))?$/)
  if (!m) {
    throw new AppError(
      ErrorCode.PluginManifestInvalid,
      'plugin.install.invalid_github_spec'
    )
  }
  const [, owner, repo, tag] = m
  return tag ? { owner, repo, tag } : { owner, repo }
}

interface GhReleaseResponse {
  tag_name: string
  assets: Array<{ name: string; browser_download_url: string }>
}

export interface DownloadResult {
  tag: string
  assetName: string
}

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Pass the spec as owner/repo or owner/repo@tag with no prefix and no whitespace.
  2. Strip a leading 'github:' and trim whitespace before calling parseGithubSpec.
  3. Validate the spec with the same regex before submitting it to the installer.

Example fix

// before
parseGithubSpec('github:owner/repo@v1.0') // no match
// after
const spec = raw.replace(/^github:/, '').trim()
parseGithubSpec(spec) // 'owner/repo@v1.0'
Defensive patterns

Strategy: validation

Validate before calling

const GITHUB_SPEC_RE = /^([^/@\s]+)\/([^/@\s]+)(?:@(.+))?$/
function normalizeGithubSpec(raw: string): string {
  const spec = raw.replace(/^github:/, '').trim()
  if (!GITHUB_SPEC_RE.test(spec)) {
    throw new Error(`invalid github spec: ${raw} (expected owner/repo[@tag])`)
  }
  return spec
}

Type guard

function isValidGithubSpec(raw: string): boolean {
  return /^([^/@\s]+)\/([^/@\s]+)(?:@(.+))?$/.test(raw.replace(/^github:/, '').trim())
}

Try / catch

try {
  parseGithubSpec(spec)
} catch (e) {
  if (e.code === 'PLUGIN_MANIFEST_INVALID' && /invalid_github_spec/.test(e.message)) {
    // prompt the user for owner/repo[@tag]; strip 'github:' and whitespace before retrying
  } else throw e
}

Prevention

When it happens

Trigger: Caller passes 'foo' (no slash), 'foo/bar/baz', ' owner/repo' (leading space), '@owner/repo', or 'github:owner/repo' without stripping the prefix.

Common situations: User pastes a full GitHub URL or the 'github:' shorthand into the install field; whitespace from copy-paste; extra trailing slash or path.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/e8c3a3c64bbfea18. Report an issue: GitHub.