mastra-ai/mastra · error · MaterializeError

clone-failed

clone-failed

Error message

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

What it means

Before building any git command, materializeRepo validates the repo full name against /^\w.-]+\/[\w.-]+$/ (owner/name). A value that fails the regex is rejected with this MaterializeError (code clone-failed) as defense in depth, so a malformed DB row can never inject shell or URL content into a clone command.

Source

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

/**
 * Materialize the repo inside the user's sandbox. Clones on first open, pulls on
 * re-open. Always scrubs the install token from the remote afterwards and sets
 * `materialized_at` on the per-user sandbox binding row.
 */
export async function materializeRepo(options: MaterializeRepoOptions): Promise<void> {
  return timedPhase('workspace.materialize', () => materializeRepoImpl(options));
}

async function materializeRepoImpl(options: MaterializeRepoOptions): Promise<void> {
  const { row: sandboxRow, repoInfo, sandbox, token, storage } = options;
  const workdir = sandboxRow.sandboxWorkdir;
  const repo = repoInfo.repoFullName;

  // 0. Defense in depth: never build a git command from values that aren't
  // strictly shaped, even if a malformed row reached the DB. Inputs are also
  // validated at the route boundary before storage.
  if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) {
    throw new MaterializeError(`Refusing to materialize: invalid repo full name '${repo}'.`, 'clone-failed');
  }
  if (!/^[A-Za-z0-9_./-]+$/.test(repoInfo.defaultBranch)) {
    throw new MaterializeError(
      `Refusing to materialize: invalid default branch '${repoInfo.defaultBranch}'.`,
      'clone-failed',
    );
  }

  // 1. Preflight: git must be installed in the sandbox template.
  const gitVersion = await sh(sandbox, 'git --version');
  if (gitVersion.exitCode !== 0) {
    throw new MaterializeError(
      'git is not installed in the sandbox. The sandbox template must include git.',
      'git-missing',
    );
  }

  const authUrl = tokenUrl(repo, token);

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Fix the stored repoFullName to the canonical 'owner/name' slug form and re-run materialization.
  2. Ensure route-boundary validation stores only owner/name (strip protocol/host before persisting).
  3. Re-sync the repository record from the GitHub API to repair malformed names.

Example fix

// before
repoInfo = { repoFullName: 'https://github.com/acme/widgets' };
// after
repoInfo = { repoFullName: 'acme/widgets' };
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

const isRepoFullName = (v: unknown): v is string => typeof v === 'string' && /^[\w.-]+\/[\w.-]+$/.test(v);

Try / catch

try {
  await materializeRepo(options);
} catch (err) {
  if (err instanceof MaterializeError && err.code === 'clone-failed' && err.message.includes('invalid repo full name')) {
    // repair the stored record, then retry
    await resyncRepository(options.repoInfo.id);
    return materializeRepo(options);
  }
  throw err;
}

Prevention

When it happens

Trigger: materializeRepo() called with repoInfo.repoFullName such as 'owner/', '/repo', 'owner name/repo', 'owner/repo#main', or a value with spaces, slashes beyond one, or URL characters — typically from a corrupted or hand-edited repository row.

Common situations: Local dev DB seeded by hand, a migration that stored full clone URLs instead of owner/name, or upstream data returning display names rather than slugs.

Related errors


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