{"record":{"id":"8fe0eda32297d830","repo":"mastra-ai/mastra","slug":"clone-failed","errorCode":"clone-failed","errorMessage":"Refusing to materialize: invalid repo full name '${repo}'.","messagePattern":"Refusing to materialize: invalid repo full name '(.+?)'\\.","errorType":"error_code","errorClass":"MaterializeError","httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/integrations/github/sandbox.ts","lineNumber":245,"sourceCode":"/**\n * Materialize the repo inside the user's sandbox. Clones on first open, pulls on\n * re-open. Always scrubs the install token from the remote afterwards and sets\n * `materialized_at` on the per-user sandbox binding row.\n */\nexport async function materializeRepo(options: MaterializeRepoOptions): Promise<void> {\n  return timedPhase('workspace.materialize', () => materializeRepoImpl(options));\n}\n\nasync function materializeRepoImpl(options: MaterializeRepoOptions): Promise<void> {\n  const { row: sandboxRow, repoInfo, sandbox, token, storage } = options;\n  const workdir = sandboxRow.sandboxWorkdir;\n  const repo = repoInfo.repoFullName;\n\n  // 0. Defense in depth: never build a git command from values that aren't\n  // strictly shaped, even if a malformed row reached the DB. Inputs are also\n  // validated at the route boundary before storage.\n  if (!/^[\\w.-]+\\/[\\w.-]+$/.test(repo)) {\n    throw new MaterializeError(`Refusing to materialize: invalid repo full name '${repo}'.`, 'clone-failed');\n  }\n  if (!/^[A-Za-z0-9_./-]+$/.test(repoInfo.defaultBranch)) {\n    throw new MaterializeError(\n      `Refusing to materialize: invalid default branch '${repoInfo.defaultBranch}'.`,\n      'clone-failed',\n    );\n  }\n\n  // 1. Preflight: git must be installed in the sandbox template.\n  const gitVersion = await sh(sandbox, 'git --version');\n  if (gitVersion.exitCode !== 0) {\n    throw new MaterializeError(\n      'git is not installed in the sandbox. The sandbox template must include git.',\n      'git-missing',\n    );\n  }\n\n  const authUrl = tokenUrl(repo, token);","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/integrations/github/sandbox.ts#L227-L263","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the stored repoFullName to the canonical 'owner/name' slug form and re-run materialization.","Ensure route-boundary validation stores only owner/name (strip protocol/host before persisting).","Re-sync the repository record from the GitHub API to repair malformed names."],"exampleFix":"// before\nrepoInfo = { repoFullName: 'https://github.com/acme/widgets' };\n// after\nrepoInfo = { repoFullName: 'acme/widgets' };","handlingStrategy":"validation","validationCode":"const REPO_FULL_NAME = /^[\\w.-]+\\/[\\w.-]+$/;\nif (!REPO_FULL_NAME.test(repoFullName)) throw new Error(`invalid repo full name: ${repoFullName}`);","typeGuard":"const isRepoFullName = (v: unknown): v is string => typeof v === 'string' && /^[\\w.-]+\\/[\\w.-]+$/.test(v);","tryCatchPattern":"try {\n  await materializeRepo(options);\n} catch (err) {\n  if (err instanceof MaterializeError && err.code === 'clone-failed' && err.message.includes('invalid repo full name')) {\n    // repair the stored record, then retry\n    await resyncRepository(options.repoInfo.id);\n    return materializeRepo(options);\n  }\n  throw err;\n}","preventionTips":["Validate owner/name at every write boundary (API, sync jobs, migrations) before persisting.","Never store full clone URLs in the repoFullName column; keep slug form only.","Add a DB constraint or zod schema enforcing the owner/name shape.","Seed dev databases via the normal sync path rather than hand-edited rows."],"tags":["validation","git-clone","injection-guard","github"],"backgroundTag":"invalid-input-rejected","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}