mastra-ai/mastra · error · MaterializeError

push-failed

push-failed

Error message

Refusing to push: invalid repo full name '${repoFullName}'.

What it means

withInstallToken validates the repo full name against /^[\w.-]+\/[\w.-]+$/ before rewriting `origin` to a tokenized URL, throwing MaterializeError('push-failed') on mismatch. This guard prevents embedding an untrusted string into a remote URL (and later into a shell command), which could leak the install token or execute injection.

Source

Thrown at mastracode/factory/src/integrations/github/sandbox.ts:688

 * **always** scrub the remote back to the tokenless URL afterwards. The token
 * therefore only ever lives in the remote URL for the duration of the
 * operation and is never left in the VM's git config.
 *
 * Once the tokenized URL is installed a failed scrub may leave the token
 * persisted, so it is always surfaced: on its own after a successful `fn`,
 * appended to `fn`'s own error otherwise — `fn`'s error is never replaced.
 * Only a failed set-url (the token never reached the remote) downgrades the
 * scrub to best-effort.
 */
export async function withInstallToken<T>(
  sandbox: ExecutableSandbox,
  workdir: string,
  repoFullName: string,
  token: string,
  fn: () => Promise<T>,
): Promise<T> {
  if (!/^[\w.-]+\/[\w.-]+$/.test(repoFullName)) {
    throw new MaterializeError(`Refusing to push: invalid repo full name '${repoFullName}'.`, 'push-failed');
  }

  const setUrl = await sh(
    sandbox,
    `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(tokenUrl(repoFullName, token))}`,
  );
  if (setUrl.exitCode !== 0) {
    // Best-effort scrub even though set-url failed, then surface the failure.
    await scrubRemote(sandbox, workdir, repoFullName, false);
    throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr.trim()}`, 'push-failed');
  }

  let result: T;
  try {
    result = await fn();
  } catch (primary) {
    throw await scrubbedFailure(sandbox, workdir, repoFullName, true, primary, 'push-failed');
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Normalize the repo full name to 'owner/repo' form before calling pushBranch
  2. Strip protocol/host if you have a full URL (extract the last two path segments)
  3. Trim whitespace and reject empty strings at the input boundary

Example fix

// before
await pushBranch(sandbox, workdir, 'https://github.com/acme/widgets.git', branch, token);
// after
const repoFullName = 'acme/widgets';
await pushBranch(sandbox, workdir, repoFullName, branch, token);
Defensive patterns

Strategy: validation

Validate before calling

if (!/^[\w.-]+\/[\w.-]+$/.test(repoFullName)) {
  throw new Error(`invalid repo full name: ${repoFullName}`);
}

Type guard

function isValidRepoFullName(name: string): name is `${string}/${string}` {
  return /^[\w.-]+\/[\w.-]+$/.test(name);
}

Try / catch

try {
  await pushBranch(sandbox, workdir, branch, token, repoFullName);
} catch (e) {
  if (e instanceof MaterializeError && e.code === 'push-failed' && e.message.includes('invalid repo full name')) {
    repoFullName = normalizeRepoFullName(repoFullName);
    return pushBranch(sandbox, workdir, branch, token, repoFullName);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling pushBranch with a repoFullName that is empty, contains spaces/slashes beyond the single owner/name separator, or shell metacharacters — e.g. a malformed value parsed from a git remote or an API payload.

Common situations: Parsing owner/name from a URL incorrectly (leaving 'https://github.com/' prefix), user-supplied repo names with typos, or passing a full clone URL instead of 'owner/repo'.

Related errors


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