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

  1. Trim and validate the base branch before calling createPullRequest
  2. Resolve the repo's default branch explicitly (via API) rather than guessing
  3. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/37c10964f0741aa1. Report an issue: GitHub.