mastra-ai/mastra · error

Repository link ${session.projectRepositoryId} is incomplete

Error message

Repository link ${session.projectRepositoryId} is incomplete

What it means

After fetching the project-repository link, createWorkspaceFactory loads its associated connection and repository in parallel. If either is missing, the link is considered incomplete and this Error is thrown — a workspace cannot be provisioned without both.

Source

Thrown at mastracode/factory/src/workspace.ts:295

    }
    if (!sandboxConfig || !github) {
      throw new Error('GitHub and a sandbox callback are required to create a Factory session workspace');
    }
    const createSessionSandboxInstance = sandboxConfig;

    const storage = github.sourceControlStorage;
    const projectRepository = await storage.projectRepositories.get({
      orgId: session.orgId,
      id: session.projectRepositoryId,
    });
    if (!projectRepository) throw new Error(`Repository link ${session.projectRepositoryId} was not found`);
    // The remaining reads only depend on the repository link — issue them in
    // parallel instead of paying four sequential storage round-trips.
    const [connection, repository] = await Promise.all([
      storage.connections.get({ orgId: session.orgId, id: projectRepository.connectionId }),
      storage.repositories.get({ orgId: session.orgId, id: projectRepository.repositoryId }),
    ]);
    if (!connection || !repository) throw new Error(`Repository link ${session.projectRepositoryId} is incomplete`);
    const installation = await storage.installations.get({ orgId: session.orgId, id: connection.installationId });
    if (!installation) throw new Error(`GitHub installation ${connection.installationId} was not found`);
    const repoFullName = repository.slug;

    // Construct (or fetch) the session's memoized sandbox instance.
    // Construction is cheap and side-effect-free by the callback contract —
    // the VM is provisioned on `start()`, which only the materialization
    // pipeline calls. The workdir is never persisted or trusted from storage
    // or client input (the stale-workdir incident class came from reusing
    // `session.sandboxWorkdir` written under a different provider): local
    // sandboxes derive it at construction, remote sandboxes clone into the
    // VM's own home so it resolves lazily at first start.
    // `runSetupOn` references `runSessionSetup`, defined below — it is only
    // invoked during start, long after this closure fully initializes.
    const runSetupOn = (target: unknown, workdir: string) =>
      runSessionSetup(requireExec(target as WorkspaceSandbox), workdir);
    const guardedSetup = createSessionSetupHook(runSetupOn, session.id, repoFullName);
    // Composed start hook: marker-guarded repo setup, then per-start

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Recreate the missing connection or repository record, then create a fresh complete repository link and point the session at it
  2. Check each piece: storage.connections.get and storage.repositories.get to identify which side is missing
  3. Delete the orphaned projectRepository link to prevent further resolution attempts against it

Example fix

// before
const factory = await createWorkspaceFactory({ session }); // link's connection was deleted
// after
const link = await storage.projectRepositories.get({ orgId: session.orgId, id: session.projectRepositoryId });
const conn = link && await storage.connections.get({ orgId: session.orgId, id: link.connectionId });
if (!conn) {
  const newConn = await reconnectGithubApp(); // recreate connection
  const newLink = await storage.projectRepositories.create({ orgId: session.orgId, connectionId: newConn.id, repositoryId: link.repositoryId });
}
const factory = await createWorkspaceFactory({ session });
Defensive patterns

Strategy: validation

Validate before calling

const link = await storage.projectRepositories.get({ orgId: session.orgId, id: session.projectRepositoryId });
if (link) {
  const [conn, repo] = await Promise.all([
    storage.connections.get({ orgId: session.orgId, id: link.connectionId }),
    storage.repositories.get({ orgId: session.orgId, id: link.repositoryId }),
  ]);
  if (!conn || !repo) throw new Error('Repository link incomplete');
}

Type guard

null

Try / catch

try {
  factory = await createWorkspaceFactory({ session, user });
} catch (e) {
  if (e.message.includes('is incomplete')) {
    // recreate the missing connection/repository and relink
  } else throw e;
}

Prevention

When it happens

Trigger: The projectRepository row references a connectionId or repositoryId that no longer exists (connection revoked/deleted, repository record removed, or link created with dangling IDs).

Common situations: A GitHub App connection being uninstalled/deleted while links still point at it, partial cleanup scripts deleting repositories but not their links, or storage restored inconsistently.

Related errors


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