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
- Verify the repo exists by opening the URL in a browser and check the response body in the error for the real reason
- Supply a GitHub personal access token for private repos or rate limits
- Re-authenticate / regenerate an expired or revoked token
- 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
- Supply a GITHUB token for private repos and to avoid 60 req/hour unauthenticated limits
- Verify repo URL resolves before installing
- Handle 403 rate-limit bodies by backing off
- Check x-ratelimit-remaining before batch operations
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
- Failed to retrieve skeleton metadata
- Unexpected response when fetching openid-configuration: ${re
- unexpected response ${response.statusText}
- Unexpected response ${response.statusText}
- Unable to retrieve docker-compose file - ${response.status}
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/4fd7fdca8559c819.
Report an issue: GitHub.