How the Gitea Integration Syncs Issues and Labels in Kaneo

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, 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.

// 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, a mutation hook wraps the API call.

// 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, 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 locates the linked task by the external issue ID and updates its labels accordingly.

// 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. This utility instantiates an authenticated client via createGiteaClient and ensures the label exists on the remote issue.

// 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, assign-label-to-task.ts, and 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. 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, which fetches open issues via listIssues and creates Kaneo tasks with createTaskFromGiteaIssue.
  • Real-time updates flow through webhook-handler.ts, which routes Gitea events to specific handlers like issue-labeled.ts.
  • Bidirectional label sync uses 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 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. 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 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, 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 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 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →