Budibase/budibase · error · Error

Error: ${err} - ${respErr}

Error message

Error: ${err} - ${respErr}

What it means

request() wraps fetchWithBlacklist and treats any HTTP status >= 300 as a failure, re-throwing the upstream response body inside 'Error: <context> - <body>'. The context string (e.g. 'Repository not found') comes from the caller, so this error means the GitHub API call itself failed, not local validation.

Source

Thrown at packages/server/src/api/controllers/plugin/github.ts:31

    throw new Error("Invalid Github URL")
  }

  if (parsed.protocol !== "https:" || parsed.hostname !== "github.com") {
    throw new Error("The plugin origin must be from Github")
  }

  return parsed
}

export async function request(
  url: string,
  headers: Record<string, string> = {},
  err: string
) {
  const response = await coreUtils.fetchWithBlacklist(url, { headers })
  if (response.status >= 300) {
    const respErr = await response.text()
    throw new Error(`Error: ${err} - ${respErr}`)
  }
  return response.json()
}

export async function githubUpload(url: string, name = "", token = "") {
  let githubUrl = parseGithubUrl(url).toString()
  let path: string | undefined

  if (url.includes(".git")) {
    githubUrl = url.replace(".git", "")
  }

  githubUrl = parseGithubUrl(githubUrl).toString()

  const githubApiUrl = githubUrl.replace(
    "https://github.com/",
    "https://api.github.com/repos/"
  )

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Verify the repo exists by opening the URL in a browser and check the response body in the error for the real reason
  2. Supply a GitHub personal access token for private repos or rate limits
  3. Re-authenticate / regenerate an expired or revoked token
  4. Retry later if the body indicates 403 rate limit or 5xx

Example fix

// before
const details = await githubUpload('https://github.com/org/private-plugin')
// after
const details = await githubUpload('https://github.com/org/private-plugin', 'plugin', process.env.GITHUB_TOKEN)
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`)
if (res.status === 404) throw new Error('Repo not found or private')
if (res.status === 403 && res.headers.get('x-ratelimit-remaining') === '0') throw new Error('GitHub rate limit exceeded')

Try / catch

try {
  await installPlugin({ source: 'GITHUB', url })
} catch (err) {
  if (/^Error: .* - /.test(err.message)) {
    const body = err.message.split(' - ')[1]
    if (body.includes('Not Found')) console.error('Repo missing or needs a token')
    else if (body.includes('rate limit')) console.error('Retry later or add a token')
  }
}

Prevention

When it happens

Trigger: githubUpload calling the GitHub API repo endpoint or latest-release endpoint and receiving 3xx/4xx/5xx — most commonly 404 for a missing/private repo without a token, or 403 rate-limit from unauthenticated GitHub API usage (60 req/hour).

Common situations: Private repository without supplying a GITHUB token; typo in org/repo name; repository renamed or deleted; unauthenticated rate limiting on shared IPs; GitHub API downtime.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/4fd7fdca8559c819. Report an issue: GitHub.