# How the GitHub Integration Works in Kaneo: A Complete Technical Breakdown

> Explore the technical details of Kaneo's GitHub integration. Learn how Octokit clients, JSON metadata, and type-safe endpoints ensure seamless repository connection and permission validation.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: deep-dive
- Published: 2026-08-29

---

**Kaneo connects projects to GitHub repositories through a GitHub App that uses installation-scoped Octokit clients to validate permissions, store repository metadata as JSON, and expose type-safe REST endpoints for the React frontend.**

The GitHub integration in the [usekaneo/kaneo](https://github.com/usekaneo/kaneo) repository is implemented as a full-stack feature spanning the API backend, a typed HTTP client library, and React UI components. It enables bidirectional synchronization between Kaneo projects and GitHub repositories by leveraging GitHub App installations, stored configuration objects, and verification workflows that ensure proper permissions before any data exchange occurs.

## Architecture Overview

The integration is organized into three distinct layers that handle authentication, data transfer, and user interaction:

- **API Backend**: Validates and stores integration data, communicates with the GitHub App to resolve installation IDs, and exposes typed OpenAPI routes via Hono controllers located in `apps/api/src/github-integration/controllers/*`.

- **Typed Client**: The `@kaneo/libs` package generates a type-safe HTTP client that the web UI consumes for all GitHub integration operations, ensuring end-to-end type safety between the frontend and backend.

- **Web UI**: React components and hooks in [`apps/web/src/components/project/github-integration-settings.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/project/github-integration-settings.tsx) manage the connection interface, calling the typed client through small fetcher helpers in `apps/web/src/fetchers/github-integration/*`.

## Creating a GitHub Integration

When a user submits a repository owner and name through the settings UI, the frontend invokes the `createGithubIntegration` fetcher:

```typescript
// apps/web/src/fetchers/github-integration/create-github-integration.ts
import { client } from "@kaneo/libs";

export async function createGithubIntegration(
  projectId: string,
  data: { repositoryOwner: string; repositoryName: string },
) {
  const resp = await client["github-integration"].project[":projectId"].$post({
    param: { projectId },
    json: data,
  });
  if (!resp.ok) throw new Error(await resp.text());
  return resp.json();
}

```

The request reaches the controller at [`apps/api/src/github-integration/controllers/create-github-integration.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/github-integration/controllers/create-github-integration.ts), which executes the following validation sequence:

1. **GitHub App initialization**: Calls `getGithubApp()` to ensure the App is configured with environment variables (`GITHUB_APP_ID`, `GITHUB_PRIVATE_KEY`, `GITHUB_WEBHOOK_SECRET`).

2. **Project validation**: Confirms the target project exists in the database.

3. **Uniqueness constraint**: Queries existing integrations to prevent linking the same repository to multiple projects.

4. **Installation ID discovery**: Uses `githubApp.octokit.rest.apps.getRepoInstallation` to fetch the numeric installation ID for the repository.

5. **Persistence**: Stores the configuration—including owner, name, installationId, and default settings—as a JSON object in `integrationTable` with `type: "github"`.

## Retrieving Integration Data

The frontend polls the current integration state using `getGithubIntegration`:

```typescript
// apps/web/src/fetchers/github-integration/get-github-integration.ts
import { client } from "@kaneo/libs";

export async function getGithubIntegration(projectId: string) {
  const resp = await client["github-integration"].project[":projectId"].$get({
    param: { projectId },
  });
  if (!resp.ok) throw new Error(await resp.text());
  return resp.json();
}

```

The controller at [`apps/api/src/github-integration/controllers/get-github-integration.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/github-integration/controllers/get-github-integration.ts) reads the stored row, parses the JSON configuration, and returns a normalized shape that merges default values (such as `branchPattern`) when fields are missing from the stored record.

## Verifying GitHub App Installation

Before executing sync operations, Kaneo validates that the GitHub App remains installed and authorized. The verification logic resides in [`apps/api/src/github-integration/controllers/verify-github-installation.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/github-integration/controllers/verify-github-installation.ts):

```typescript
async function verifyGithubInstallation({ repositoryOwner, repositoryName }) {
  const githubApp = getGithubApp();
  // ... validation logic ...
}

```

The verification process performs several checks:

- **Installation status**: Detects 404 responses from `apps.getRepoInstallation` and returns an installation URL for the user.

- **Repository accessibility**: Obtains an installation-scoped Octokit client via `getInstallationOctokit` and calls `rest.repos.get` to confirm the repository exists and is accessible.

- **Permission validation**: Ensures the installation grants **write** or **admin** permissions on the `issues` scope, which is the minimum required permission for Kaneo operations.

- **User guidance**: Returns `settingsUrl` and `installationUrl` properties to direct users to GitHub's configuration pages when permissions are missing.

## Core Utilities and Configuration

The [`apps/api/src/plugins/github/utils/github-app.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/utils/github-app.ts) module centralizes GitHub App lifecycle management. It reads private keys from environment variables, initializes the `Octokit.App` instance, and exposes helper methods:

- `getInstallationOctokit`: Returns an authenticated client scoped to a specific installation ID.

- `getInstallationIdForRepo`: Resolves installation IDs for given repository coordinates.

Default configuration values—such as `branchPattern` regex and `commentTaskLinkOnGitHubIssue` flags—are defined in [`apps/api/src/plugins/github/config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/config.ts). These defaults are merged with stored user configurations during read operations to ensure API stability.

## Practical Implementation Examples

### Checking Installation Status

Before importing issues, verify the App is properly installed and authorized:

```typescript
import { client } from "@kaneo/libs";

async function checkInstallation(owner: string, repo: string) {
  const { data } = await client["github-integration"]
    .verify["verify-github-installation"].$post({
      json: { repositoryOwner: owner, repositoryName: repo },
    });

  if (!data.isInstalled) {
    console.warn("App not installed –", data.installationUrl);
  } else if (!data.hasRequiredPermissions) {
    console.warn("Missing permissions:", data.missingPermissions);
  } else {
    console.log("All good! Installation ID:", data.installationId);
  }
}

```

### Importing GitHub Issues

Once verified, import existing issues into Kaneo tasks:

```typescript
import { client } from "@kaneo/libs";

async function importIssues(projectId: string, repoOwner: string, repoName: string) {
  const { data } = await client["github-integration"]
    .import["import-github-issues"].$post({
      param: { projectId },
      json: { repositoryOwner: repoOwner, repositoryName: repoName },
    });

  console.log(`${data.importedCount} issues imported`);
}

```

## Summary

- Kaneo's GitHub integration uses a **GitHub App** rather than OAuth tokens, enabling granular permission control and organization-level access.

- The architecture separates concerns across **API controllers**, a **typed Hono client**, and **React hooks/components** for maintainable full-stack operations.

- **Installation IDs** are resolved dynamically via `getRepoInstallation` and stored in JSON configuration rows within the `integrationTable`.

- **Permission verification** occurs before every sync operation, checking for write/admin access on the issues scope and providing actionable URLs for remediation.

- **Default configurations** in [`apps/api/src/plugins/github/config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/config.ts) ensure backward compatibility when reading legacy integration records.

## Frequently Asked Questions

### What permissions does Kaneo require from the GitHub App?

Kaneo requires **write** or **admin** permissions on the `issues` scope. The verification controller in [`verify-github-installation.ts`](https://github.com/usekaneo/kaneo/blob/main/verify-github-installation.ts) explicitly checks for these permissions before allowing import or sync operations, and returns a `missingPermissions` array if additional access is needed.

### How does Kaneo store GitHub integration credentials?

Kaneo does not store personal access tokens. Instead, it stores the **installation ID** returned by GitHub's App API. The private key and App ID remain in environment variables (`GITHUB_PRIVATE_KEY`, `GITHUB_APP_ID`), while the database keeps only the repository metadata and installation reference in a JSON column of the `integrationTable`.

### Can the same GitHub repository connect to multiple Kaneo projects?

No. The [`create-github-integration.ts`](https://github.com/usekaneo/kaneo/blob/main/create-github-integration.ts) controller explicitly checks for existing integrations with the same owner and repository name before creating a new record, enforcing a one-to-one relationship between GitHub repositories and Kaneo projects.

### What happens if the GitHub App is uninstalled after initial setup?

The `verifyGithubInstallation` controller detects 404 errors when fetching the repository installation and returns `isInstalled: false` along with a fresh `installationUrl`. The frontend uses this response to prompt users to reinstall the App before continuing with sync operations.