different-ai/openwork · error · PluginArchRouteFailure

invalid_github_materialization_context

invalid_github_materialization_context

Error message

GitHub connector target is missing required materialization context.

What it means

Thrown (409) when a GitHub connector target lacks any of the three required materialization inputs: an installationId (from the connector instance's instanceConfigJson), a branch (from targetConfig.branch or the target's externalTargetRef), or a repositoryFullName (from targetConfig.repositoryFullName or the target's remoteId). Materialization cannot proceed against GitHub without all three.

Source

Thrown at ee/apps/den-api/src/routes/org/plugin-system/store.ts:6615

async function materializeGithubImportPlans(input: {
  connectorInstance: ReturnType<typeof serializeConnectorInstance>
  connectorSyncEventId?: ConnectorSyncEventId
  connectorTarget: ReturnType<typeof serializeConnectorTarget>
  context: PluginArchActorContext
  importPlans: Array<{ fileShaByPath?: Record<string, string>; mapping: ReturnType<typeof serializeConnectorMapping>; paths: string[] }>
  sourceRevisionRef: string
}) {
  const config = githubConnectorAppConfig()
  const targetConfig = input.connectorTarget.targetConfigJson && typeof input.connectorTarget.targetConfigJson === "object"
    ? input.connectorTarget.targetConfigJson as Record<string, unknown>
    : {}
  const branch = typeof targetConfig.branch === "string" ? targetConfig.branch : input.connectorTarget.externalTargetRef ?? ""
  const installationId = typeof input.connectorInstance.instanceConfigJson === "object" && input.connectorInstance.instanceConfigJson && typeof (input.connectorInstance.instanceConfigJson as Record<string, unknown>).installationId === "number"
    ? (input.connectorInstance.instanceConfigJson as Record<string, unknown>).installationId as number
    : null
  const repositoryFullName = typeof targetConfig.repositoryFullName === "string" ? targetConfig.repositoryFullName : input.connectorTarget.remoteId
  if (!installationId || !branch || !repositoryFullName) {
    throw new PluginArchRouteFailure(409, "invalid_github_materialization_context", "GitHub connector target is missing required materialization context.")
  }

  const token = await getGithubInstallationAccessToken({
    config,
    installationId,
  })
  const organizationId = input.context.organizationContext.organization.id
  const plannedFiles = input.importPlans.flatMap((plan) => plan.paths.map((path) => ({
    mapping: plan.mapping,
    path,
    sourceFileRevisionRef: plan.fileShaByPath?.[path] ?? null,
  })))
  const existingBindings = await Promise.all(plannedFiles.map((file) => findActiveConnectorSourceBinding({
    connectorMappingId: file.mapping.id,
    externalLocator: file.path,
    organizationId,
  })))
  const fetchResults = await fetchGithubImportFilesWithRevisionGuard({

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Complete the GitHub App installation so instanceConfigJson contains a numeric installationId.
  2. Set `branch` (or externalTargetRef) and `repositoryFullName` (or remoteId) on the connector target config, e.g. via updateConnectorTarget.
  3. Re-create the connector target through the normal install flow instead of hand-editing config so all fields are populated consistently.

Example fix

// before
await materializePlugin({ connectorTargetId }) // targetConfig = { repositoryFullName: 'org/repo' } -> no branch, no installationId
// after
await updateConnectorTarget(connectorTargetId, { config: { repositoryFullName: 'org/repo', branch: 'main' } })
await completeGithubConnectorInstall({ installationId: 12345, state }) // populates instanceConfigJson.installationId
await materializePlugin({ connectorTargetId })
Defensive patterns

Strategy: validation

Validate before calling

const installationId = instance.instanceConfigJson && typeof instance.instanceConfigJson === 'object' ? (instance.instanceConfigJson as Record<string, unknown>).installationId : null
const branch = targetConfig.branch ?? target.externalTargetRef
const repositoryFullName = targetConfig.repositoryFullName ?? target.remoteId
if (!installationId || !branch || !repositoryFullName) throw new Error('GitHub materialization requires installationId, branch, and repositoryFullName')

Type guard

function hasGithubMaterializationContext(t: { installationId: unknown; branch: unknown; repositoryFullName: unknown }): t is { installationId: number; branch: string; repositoryFullName: string } {
  return typeof t.installationId === 'number' && t.installationId > 0 && typeof t.branch === 'string' && t.branch.length > 0 && typeof t.repositoryFullName === 'string' && t.repositoryFullName.length > 0
}

Try / catch

try {
  await materializePlugin({ connectorTargetId })
} catch (e) {
  if (e instanceof PluginArchRouteFailure && e.code === 'invalid_github_materialization_context') {
    await restartGithubConnectorInstallFlow() // repopulate installationId/branch/repo
  } else throw e
}

Prevention

When it happens

Trigger: Materializing plugin code from a GitHub connector where the GitHub App was never installed (no installationId in instanceConfigJson), the target config omits `branch`, or repositoryFullName/remoteId is empty or misconfigured.

Common situations: Partial connector target setup: repo selected but branch never saved; App uninstalled from the org leaving stale instance config; manual DB edits dropping remoteId; connecting a target before completing the GitHub install callback.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/5a0f717d53c915e1. Report an issue: GitHub.