How to Set Up GitHub Integration for Bidirectional Task Sync in Kaneo
To set up GitHub integration in Kaneo, configure environment variables for a GitHub App, register the app with webhook URL <KANEO_API_URL>/github-integration/webhook, then use the REST API to link a project to a repository for automatic two-way synchronization of issues, pull requests, and comments.
Kaneo's open-source task management platform supports native GitHub integration for bidirectional task sync, allowing teams to manage work in either tool while keeping both systems consistent. This guide walks through the complete implementation based on the Kaneo source code, from environment configuration to real-time webhook processing.
Architecture Overview
Kaneo follows a controller-router-schema pattern for all integrations. The GitHub module consists of these core components:
| Component | Location | Purpose |
|---|---|---|
| API Routes | apps/api/src/github-integration/index.ts |
REST endpoints for CRUD operations and webhooks |
| Config Schema | apps/api/src/plugins/github/config.ts |
Valibot validation for integration settings |
| Database Model | apps/api/src/database/schema.ts |
integration table with type = "github" |
| Controllers | apps/api/src/github-integration/controllers/*.ts |
Business logic for each operation |
| GitHub App Wrapper | apps/api/src/plugins/github/utils/github-app.ts |
Authenticated Octokit singleton |
| Webhook Handler | apps/api/src/plugins/github/webhook-handler.ts |
Processes GitHub events and updates tasks |
| Front-End Hooks | apps/web/src/hooks/mutations/github-integration/* |
React Query integration for the UI |
The flow enforces request validation → permission checks → business logic → response at every step, using hono-openapi for type safety and OpenAPI documentation generation.
Step 1: Configure Environment Variables
Create a GitHub App and set these environment variables in your Kaneo API deployment:
GITHUB_APP_ID=your-app-id
GITHUB_APP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."
GITHUB_APP_NAME=Kaneo
GITHUB_WEBHOOK_SECRET=your-webhook-secret
These values are read by apps/api/src/plugins/github/utils/github-app.ts, which exports a getGithubApp() singleton that provides authenticated Octokit instances for API calls.
Step 2: Register Your GitHub App
- Navigate to Settings → Developer settings → GitHub Apps → New GitHub App
- Set Webhook URL to:
https://<your-kaneo-api>/github-integration/webhook - Configure Repository permissions:
- Issues: Read & Write
- Pull requests: Read & Write
- Contents: Read (for branch pattern matching)
The webhook endpoint is handled by handleGithubWebhookRoute in apps/api/src/github-integration/index.ts, which verifies the x-hub-signature-256 header before processing.
Step 3: Create the Integration via API
Link a Kaneo project to a GitHub repository using the REST API:
POST /github-integration/project/:projectId
Content-Type: application/json
Authorization: Bearer <api-key>
{
"repositoryOwner": "your-org",
"repositoryName": "your-repo"
}
The create-github-integration.ts controller performs these operations:
- Validates the request against
githubConfigSchemafromapps/api/src/plugins/github/config.ts - Verifies workspace permissions via
workspaceAccess.fromProject - Fetches the installation ID via
githubApp.octokit.rest.apps.getRepoInstallation - Stores configuration in the
integrationtable withdefaultGitHubConfigmerged values
Example: React Component for Integration Setup
import { useMutation } from "@tanstack/react-query";
import createGithubIntegration from "@/fetchers/github-integration/create-github-integration";
function GithubIntegrationForm({ projectId }: { projectId: string }) {
const createMutation = useMutation({
mutationFn: (payload: { repositoryOwner: string; repositoryName: string }) =>
createGithubIntegration(projectId, payload),
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const form = e.target as HTMLFormElement;
const repositoryOwner = (form.elements.namedItem("owner") as HTMLInputElement).value;
const repositoryName = (form.elements.namedItem("repo") as HTMLInputElement).value;
createMutation.mutate({ repositoryOwner, repositoryName });
};
return (
<form onSubmit={handleSubmit}>
<input name="owner" placeholder="GitHub organization/user" required />
<input name="repo" placeholder="Repository name" required />
<button type="submit" disabled={createMutation.isLoading}>
Connect GitHub
</button>
</form>
);
}
The fetcher at apps/web/src/fetchers/github-integration/create-github-integration.ts wraps the generated OpenAPI client:
export default async function createGithubIntegration(
projectId: string,
data: { repositoryOwner: string; repositoryName: string }
) {
const client = getClient();
const response = await client["github-integration"].project[projectId].$post({ json: data });
return response;
}
Step 4: Verify Installation Status
Optionally validate permissions before linking:
POST /github-integration/verify
Content-Type: application/json
{
"repositoryOwner": "your-org",
"repositoryName": "your-repo"
}
The verify-github-installation.ts controller checks:
- Whether the GitHub App is installed on the repository
- Required permissions are granted
- Returns detailed error messages for missing scopes
Step 5: Import Existing GitHub Issues
Bulk-import open issues as Kaneo tasks:
POST /github-integration/import-issues
Content-Type: application/json
Authorization: Bearer <api-key>
{
"projectId": "<project-id>"
}
The import-issues.ts controller:
- Fetches open issues via
octokit.rest.issues.listForRepo - Creates corresponding Kaneo tasks with GitHub metadata
- Stores
githubIssueIdfor ongoing synchronization
Step 6: Enable Real-Time Bidirectional Sync
Once configured, the integration operates automatically:
Kaneo → GitHub: Task status changes, labels, and comments are pushed to GitHub via the Octokit API.
GitHub → Kaneo: Webhook events processed by apps/api/src/plugins/github/webhook-handler.ts update tasks:
| GitHub Event | Kaneo Action |
|---|---|
issues.opened |
Create new task |
issues.edited |
Update task title/description |
issues.labeled |
Sync labels |
issues.closed |
Set task status to done |
pull_request.opened |
Create task linked to PR |
issue_comment.created |
Add comment to task |
The webhook handler verifies signatures using crypto.createHmac('sha256', webhookSecret) before dispatching events.
Configuration Schema Reference
The integration config stored in the database follows this Valibot schema from apps/api/src/plugins/github/config.ts:
export const githubConfigSchema = object({
repositoryOwner: string(),
repositoryName: string(),
installationId: number(),
branchPattern: optional(string(), "main"),
syncPullRequests: optional(boolean(), true),
syncComments: optional(boolean(), true),
});
export type GitHubConfig = Output<typeof githubConfigSchema>;
Summary
- Environment setup: Configure
GITHUB_APP_ID,GITHUB_APP_PRIVATE_KEY, andGITHUB_WEBHOOK_SECRETfor the Octokit wrapper inapps/api/src/plugins/github/utils/github-app.ts - API integration: Use
POST /github-integration/project/:projectIdto link projects, validated bygithubConfigSchemaand stored in theintegrationtable - Bidirectional sync: Webhooks to
/github-integration/webhooktriggerhandleGitHubWebhookfor GitHub→Kaneo updates; Kaneo→GitHub updates use authenticated Octokit calls - Front-end support: TanStack Query hooks in
apps/web/src/hooks/mutations/github-integration/*provide React components with type-safe API access
Frequently Asked Questions
What permissions does the GitHub App need for bidirectional task sync?
The GitHub App requires Issues and Pull requests permissions with Read & Write access, plus Repository contents with Read access. These are validated by verify-github-installation.ts and enforced when creating integrations.
How does Kaneo authenticate with GitHub?
Kaneo uses @octokit/app through a singleton wrapper in apps/api/src/plugins/github/utils/github-app.ts. The wrapper creates installation-specific Octokit instances using the app ID and private key from environment variables, then exchanges these for repository-scoped tokens via GitHub's API.
Can I sync existing GitHub issues after setup?
Yes. Call POST /github-integration/import-issues with the projectId to bulk-import open issues. The import-issues.ts controller fetches all open issues and creates linked Kaneo tasks with githubIssueId stored for ongoing synchronization.
What happens if the webhook signature verification fails?
Requests to /github-integration/webhook with invalid x-hub-signature-256 headers are rejected by handleGithubWebhookRoute before reaching the handler. The route uses crypto.timingSafeEqual to prevent timing attacks during signature comparison.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →