# How the Gitea Integration Syncs Issues and Labels in Kaneo

> Learn how the Kaneo Gitea integration syncs issues and labels bidirectionally using webhooks and label utilities for seamless project management.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: how-to-guide
- Published: 2026-08-06

---

**TLDR:** The Kaneo Gitea integration combines a bulk import controller, real-time webhook event handlers, and bidirectional label utilities to keep issues and labels synchronized between a Gitea repository and a Kaneo project.**

The `usekaneo/kaneo` codebase implements a native Gitea integration that bridges external repositories with internal project management. Understanding how the Gitea integration syncs issues and labels in Kaneo requires examining the import pipeline, the real-time webhook layer, and the shared API client that both sides use to communicate.

## Importing Existing Gitea Issues to Sync with Kaneo

When a project is first connected, users can pull historical issue data from Gitea into Kaneo. This process is driven by the import controller and triggered through the project settings UI.

### The Import Controller

In [`apps/api/src/gitea-integration/controllers/import-gitea-issues.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/gitea-integration/controllers/import-gitea-issues.ts), the `importGiteaIssues` function retrieves the project's integration settings and fetches the open issue list. It calls `listIssues` from the Gitea API layer, then iterates over the results and creates a corresponding task for each Gitea issue.

```typescript
// apps/api/src/gitea-integration/controllers/import-gitea-issues.ts
export async function importGiteaIssues(projectId: string) {
  const integration = await getGiteaIntegrationForProject(projectId);
  const issues = await listIssues(integration.baseUrl, integration.accessToken);
  for (const issue of issues) {
    await createTaskFromGiteaIssue(projectId, issue);
  }
}

```

The controller preserves titles, descriptions, comments, and labels during creation so that the imported tasks mirror their Gitea counterparts exactly.

### Triggering Imports from the Frontend

The frontend exposes the import action inside the Gitea integration settings component. In [`apps/web/src/components/project/gitea-integration-settings.tsx`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/components/project/gitea-integration-settings.tsx), a mutation hook wraps the API call.

```tsx
// apps/web/src/components/project/gitea-integration-settings.tsx
const { mutateAsync: importIssues, isPending: isImporting } =
  useMutation(importGiteaIssues);

const handleImportIssues = async () => {
  await importIssues(projectId);
};

```

When the user clicks the import button, `handleImportIssues` executes the mutation and the backend begins the bulk sync.

## Real-Time Issue and Label Sync via Gitea Webhooks

After the initial import, Kaneo stays current by listening for Gitea webhook events. The API exposes a central handler that validates payloads and dispatches them to event-specific processors.

### Webhook Routing and Validation

Incoming requests hit [`apps/api/src/plugins/gitea/webhook-handler.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/gitea/webhook-handler.ts), which verifies the webhook signature before routing. As implemented in `usekaneo/kaneo`, this entry point inspects the event type and delegates to the appropriate handler file.

Event types such as `issue-opened`, `issue-edited`, `issue-labeled`, and `issue-comment-created` each have dedicated handlers under the webhooks directory. This design isolates event logic and prevents a single large switch block.

### Issue Event Handlers

The event handlers translate Gitea payloads into Kaneo task operations. For example, [`apps/api/src/plugins/gitea/webhooks/issue-labeled.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/gitea/webhooks/issue-labeled.ts) locates the linked task by the external issue ID and updates its labels accordingly.

```typescript
// apps/api/src/plugins/gitea/webhooks/issue-labeled.ts
export async function handleIssueLabeled(payload) {
  const task = await findTaskByExternalId(payload.issue.id);
  await assignLabelToTask(task.id, payload.label.name);
}

```

Similarly, handlers for issue edits and new comments update titles, descriptions, and thread data using the same service-layer functions that the rest of the Kaneo API uses.

## Bidirectional Label Synchronization Between Kaneo and Gitea

Labels are synchronized in both directions: Kaneo pushes its label changes to Gitea, and Gitea webhooks reflect remote label changes back into Kaneo.

### Syncing Kaneo Labels to Gitea

Whenever a label is created, assigned, or removed inside Kaneo, the label controllers invoke [`apps/api/src/plugins/gitea/utils/sync-label-to-gitea.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/gitea/utils/sync-label-to-gitea.ts). This utility instantiates an authenticated client via `createGiteaClient` and ensures the label exists on the remote issue.

```typescript
// apps/api/src/plugins/gitea/utils/sync-label-to-gitea.ts
export async function syncLabelToGitea(label: Label, issueId: string) {
  const client = await createGiteaClient(label.integration);
  const giteaLabel = await client.createLabel(label.name, label.color);
  await client.addLabelsToIssue(issueId, [giteaLabel.id]);
}

```

Controllers such as [`create-label.ts`](https://github.com/usekaneo/kaneo/blob/main/create-label.ts), [`assign-label-to-task.ts`](https://github.com/usekaneo/kaneo/blob/main/assign-label-to-task.ts), and [`delete-label.ts`](https://github.com/usekaneo/kaneo/blob/main/delete-label.ts) rely on this helper or on `removeLabelFromGitea` to keep the upstream repository consistent.

### Processing Gitea Label Webhooks

When Gitea emits an `issue-labeled` or `issue-unlabeled` event, the corresponding webhook handler updates the Kaneo task's label set. Because the handler uses `findTaskByExternalId` to locate the correct task, the mapping between Gitea issues and Kaneo tasks remains stable even when titles change.

## The Gitea API Client Layer

All HTTP communication with the Gitea host is centralized in [`apps/api/src/plugins/gitea/utils/gitea-api.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/gitea/utils/gitea-api.ts). According to the `usekaneo/kaneo` source code, this module exports `createGiteaClient` and typed methods including `listIssues` at line 401, `addLabelsToIssue` at line 328, `replaceIssueLabels`, and `createIssueComment`.

The import controller calls `listIssues` to fetch the initial batch, while label sync utilities call `addLabelsToIssue` to attach labels or `replaceIssueLabels` to overwrite the full set on a remote issue. By isolating network logic in one file, the integration maintains consistent authentication headers, error handling, and request formatting across every sync path.

## Summary

- **Bulk import** is handled by [`import-gitea-issues.ts`](https://github.com/usekaneo/kaneo/blob/main/import-gitea-issues.ts), which fetches open issues via `listIssues` and creates Kaneo tasks with `createTaskFromGiteaIssue`.
- **Real-time updates** flow through [`webhook-handler.ts`](https://github.com/usekaneo/kaneo/blob/main/webhook-handler.ts), which routes Gitea events to specific handlers like [`issue-labeled.ts`](https://github.com/usekaneo/kaneo/blob/main/issue-labeled.ts).
- **Bidirectional label sync** uses [`sync-label-to-gitea.ts`](https://github.com/usekaneo/kaneo/blob/main/sync-label-to-gitea.ts) to push Kaneo changes upstream, while webhook handlers pull Gitea label changes back down.
- **Shared API layer** in [`gitea-api.ts`](https://github.com/usekaneo/kaneo/blob/main/gitea-api.ts) provides authenticated methods used by both the import pipeline and the webhook processors.

## Frequently Asked Questions

### How does Kaneo initially import Gitea issues?

When a user triggers an import from the project settings, the frontend mutation `importGiteaIssues` calls the backend controller in [`apps/api/src/gitea-integration/controllers/import-gitea-issues.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/gitea-integration/controllers/import-gitea-issues.ts). That controller fetches open issues via `listIssues` and creates a matching task for each entry using `createTaskFromGiteaIssue`.

### How are labels kept in sync between Kaneo and Gitea?

Kaneo controllers invoke [`sync-label-to-gitea.ts`](https://github.com/usekaneo/kaneo/blob/main/sync-label-to-gitea.ts) to create or attach labels on the remote repository via `createGiteaClient`, while incoming `issue-labeled` webhooks are processed by handlers like [`apps/api/src/plugins/gitea/webhooks/issue-labeled.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/gitea/webhooks/issue-labeled.ts), which update the corresponding Kaneo task. This bidirectional flow ensures both systems remain consistent.

### What Gitea webhook events does Kaneo process?

The central [`webhook-handler.ts`](https://github.com/usekaneo/kaneo/blob/main/webhook-handler.ts) routes events such as `issue-opened`, `issue-edited`, `issue-labeled`, and `issue-comment-created` to dedicated handlers under `apps/api/src/plugins/gitea/webhooks/`. Each handler translates the payload into task or comment updates using the internal service layer.

### Which component handles all Gitea API communication?

The [`gitea-api.ts`](https://github.com/usekaneo/kaneo/blob/main/gitea-api.ts) utility encapsulates every authenticated HTTP call to the Gitea instance, exposing functions including `listIssues`, `addLabelsToIssue`, and `createIssueComment`. Both the initial import controller and the real-time webhook processors depend on this shared client.