mastra-ai/mastra · error · MaterializeError
pr-failed
pr-failed
Error message
Refusing to open PR: invalid base branch '${base}'. What it means
createPullRequest validates the base branch with isValidGitRef before invoking `gh pr create`, throwing MaterializeError('pr-failed') on failure. This guards against malformed refspecs and argument injection into the gh command line.
Source
Thrown at mastracode/factory/src/integrations/github/sandbox.ts:922
return match?.[0];
}
/**
* Open a pull request from inside the sandbox via `gh pr create`. The token is
* passed only through a per-invocation `GH_TOKEN` env scoped to the single `gh`
* process (never persisted), all arguments are shell-quoted, and the resulting
* PR URL is parsed from stdout.
*
* @param sandbox live sandbox containing the checkout
* @param workdir the worktree (or repo) path the PR head branch is checked out in
*/
export async function createPullRequest(
sandbox: ExecutableSandbox,
workdir: string,
{ token, base, head, title, body }: CreatePullRequestArgs,
): Promise<CreatePullRequestResult> {
if (!isValidGitRef(base)) {
throw new MaterializeError(`Refusing to open PR: invalid base branch '${base}'.`, 'pr-failed');
}
if (!isValidGitRef(head)) {
throw new MaterializeError(`Refusing to open PR: invalid head branch '${head}'.`, 'pr-failed');
}
await assertGhAvailable(sandbox);
// GH_TOKEN is prefixed inline so it is exported only to the single `gh`
// process and never to the wider shell session, git config, or VM env. `gh`
// is run from inside the checkout so it targets the correct repo/head branch.
const ghCommand = [
`GH_TOKEN=${shellQuote(token)} gh pr create`,
`--base ${shellQuote(base)}`,
`--head ${shellQuote(head)}`,
`--title ${shellQuote(title)}`,
`--body ${shellQuote(body ?? '')}`,
].join(' ');
const script = `cd ${shellQuote(workdir)} && ${ghCommand}`;View on GitHub (pinned to 75dd419e61)
Solutions
- Trim and validate the base branch before calling createPullRequest
- Resolve the repo's default branch explicitly (via API) rather than guessing
- Reject/sanitize names with whitespace or leading '-'
Example fix
// before
await createPullRequest(sandbox, workdir, { base: ' main', head, title, body });
// after
const base = detectedBase.trim();
if (!base) throw new Error('base branch is empty');
await createPullRequest(sandbox, workdir, { base, head, title, body }); Defensive patterns
Strategy: validation
Validate before calling
function isValidGitRefLocal(r: string): boolean {
return r.length > 0 && !r.startsWith('-') && !/[\s~^:?*\[\\]/.test(r) && !r.includes('..');
}
if (!isValidGitRefLocal(base)) throw new Error(`invalid base branch: ${JSON.stringify(base)}`); Type guard
function isValidBaseBranch(b: string): b is string {
return /^[\w.-]+(\/[\w.-]+)*$/.test(b) && !b.startsWith('-');
} Try / catch
try {
await createPullRequest(sandbox, workdir, args);
} catch (e) {
if (e instanceof MaterializeError && e.code === 'pr-failed' && e.message.includes('invalid base branch')) {
// resolve default branch via API and retry
}
throw e;
} Prevention
- Resolve the default branch from the GitHub API instead of hardcoding
- Trim all branch inputs; reject empty strings early
- Use a shared slugify/validator for every branch value
When it happens
Trigger: Calling createPullRequest with a base containing spaces, leading dashes, control characters, or an empty string — usually a value derived from untrusted or unparsed input.
Common situations: Parsing the base from a diff/PR URL incorrectly; default-branch detection returning an empty value; branch names copied with trailing whitespace.
Related errors
- push-failed
- Pull request must belong to ${expectedRepo}.
- Pull request repository does not match the active project re
- A review comment requires commitId, path, line, and side unl
- GitHub ${resource} id must be a positive integer.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/37c10964f0741aa1.
Report an issue: GitHub.