# How YAML Configuration and Thread Management Enable Topic-Based Routing in Claude Code Telegram

> Discover how Claude Code Telegram uses YAML configuration and thread management for efficient topic-based routing. See how project definitions and forum topics are mapped.

- Repository: [Richard A/claude-code-telegram](https://github.com/richardatct/claude-code-telegram)
- Tags: internals
- Published: 2026-02-20

---

**The Claude Code Telegram bot routes commands to specific projects by combining a YAML-backed Project Registry that validates project definitions with a Thread Manager that maintains persistent mappings between project slugs and Telegram forum topics.**

The RichardAtCT/claude-code-telegram repository implements isolated project contexts using Telegram's native forum topics. Through a configuration-driven **Project Registry** and a SQLite-backed **Thread Manager**, the system achieves deterministic topic-based routing that connects incoming messages to their corresponding file system paths and Claude sessions.

## Architecture of the Routing System

The routing mechanism relies on two tightly coupled subsystems that bridge YAML configuration with Telegram's forum topic API.

### Project Registry (YAML Configuration)

Located in [`src/projects/registry.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/projects/registry.py), the registry loads and validates project definitions from a YAML file at startup. The `load_project_registry` function (lines 45‑62) validates each entry for required fields—**unique slug**, **name**, and **relative path**—and raises descriptive errors for duplicates or missing data (lines 74‑106). 

The resulting `ProjectRegistry` object indexes all enabled projects by their slug (lines 24‑27, 33‑40), enabling O(1) lookups during runtime routing. Each project definition includes an `absolute_path` property that resolves the relative path against an approved root directory, ensuring filesystem access remains sandboxed.

### Project Thread Manager

The `ProjectThreadManager` class in [`src/projects/thread_manager.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/projects/thread_manager.py) maintains the bi-directional mapping between project slugs and Telegram forum topics. It uses `ProjectThreadRepository` to persist these associations in a SQLite `project_threads` table via the `ProjectThreadModel`.

The manager provides two critical operations: `sync_topics` (line 49) for administrative synchronization, and `resolve_project` (lines 58‑70) for runtime request routing.

## The Topic-Based Routing Workflow

When the bot processes commands, it follows a four-stage pipeline that ensures every message reaches the correct project context.

### 1. Configuration Loading at Startup

During initialization, the bot reads [`config/projects.example.yaml`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/config/projects.example.yaml) (or a user-supplied configuration path) through `load_project_registry`. This establishes the authoritative list of **enabled** projects. Any project missing required fields or using a duplicate slug triggers an immediate validation error, preventing ambiguous routing configurations.

### 2. Topic Synchronization

When an administrator invokes `/sync_threads`, the `ProjectThreadManager.sync_topics` method iterates over `registry.list_enabled()` (line 49). For each project:

- **Existing mappings**: The `_sync_existing_mapping` helper validates that the Telegram topic remains usable. If the topic was closed, it reopens it; if the project name changed in YAML, it renames the topic to maintain consistency.
- **New mappings**: The `_create_and_map_topic` method calls `bot.create_forum_topic` (lines 199‑201) to generate a new forum topic, then persists the association via `ProjectThreadRepository.upsert_mapping`.

### 3. Runtime Message Routing

When a user sends a message within a forum topic, the bot extracts `chat_id` and `message_thread_id` from the update object. It calls `resolve_project(chat_id, thread_id)` (lines 58‑70), which queries the SQLite mapping table to retrieve the `project_slug`. The manager then looks up the full `ProjectDefinition` in the registry via `registry.get_by_slug`, obtaining the `absolute_path` required to execute commands in the correct directory context.

### 4. Stale Topic Cleanup

After processing all enabled projects, `sync_topics` identifies mappings for projects no longer present in the registry. The manager closes these obsolete Telegram topics using `bot.close_forum_topic` (lines 1004‑1006) and removes their database entries, ensuring the forum structure mirrors the current YAML configuration exactly.

## Implementation Examples

### Loading the Project Registry

```python
from pathlib import Path
from src.projects.registry import load_project_registry

config_path = Path("config/projects.example.yaml")
approved_dir = Path("/approved/root")
registry = load_project_registry(config_path, approved_dir)

# Access all enabled projects

for proj in registry.list_enabled():
    print(proj.slug, proj.name, proj.absolute_path)

```

*Relevant source:* `load_project_registry` validates and builds the registry – see lines 45‑62, 71‑84, 99‑122 in **src/projects/registry.py**.

### Synchronizing Topics for a Chat

```python
from telegram import Bot
from src.projects.thread_manager import ProjectThreadManager
from src.storage.repositories import ProjectThreadRepository

bot = Bot(token="YOUR_TOKEN")
chat_id = -1001234567890  # Telegram forum ID

manager = ProjectThreadManager(registry, ProjectThreadRepository())
result = await manager.sync_topics(bot, chat_id)

print(result)  # TopicSyncResult shows created/renamed/closed counts

```

*Relevant source:* `ProjectThreadManager.sync_topics` loops over enabled projects and creates or updates topics – see lines 45‑78, 90‑101 in **src/projects/thread_manager.py**.

### Resolving a Project from an Incoming Message

```python
async def handle_message(update, context):
    chat_id = update.effective_chat.id
    thread_id = update.message.message_thread_id

    project = await manager.resolve_project(chat_id, thread_id)
    if project:
        # Route to the project's directory

        cwd = project.absolute_path
        # ...process command in this context...

```

*Relevant source:* `resolve_project` fetches the mapping and returns the matched `ProjectDefinition` – see lines 58‑70 in **src/projects/thread_manager.py**.

## Key Files and Components

| File | Purpose |
|------|---------|
| [`src/projects/registry.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/projects/registry.py) | Loads and validates the YAML project list, provides lookup by slug via `load_project_registry`. |
| [`src/projects/thread_manager.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/projects/thread_manager.py) | Manages persistent mappings between projects and Telegram forum topics; implements `sync_topics` and `resolve_project`. |
| [`src/storage/models.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/storage/models.py) | Defines `ProjectThreadModel` for persisting topic mappings. |
| [`src/storage/repositories.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/storage/repositories.py) | Provides `ProjectThreadRepository` for CRUD operations on the mapping table. |
| [`config/projects.example.yaml`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/config/projects.example.yaml) | Example YAML configuration consumed by the registry loader. |

## Summary

- The **Project Registry** in [`src/projects/registry.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/projects/registry.py) validates YAML configurations at startup and indexes projects by unique slugs for fast retrieval.
- The **Thread Manager** in [`src/projects/thread_manager.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/projects/thread_manager.py) guarantees a one-to-one relationship between each enabled project and a dedicated Telegram forum topic.
- **Topic synchronization** handles the full lifecycle of forum topics—creating new ones, renaming changed projects, reopening closed threads, and cleaning up stale mappings.
- **Runtime routing** uses `resolve_project` to map incoming chat threads to their corresponding project definitions, enabling isolated command execution within the correct filesystem context.

## Frequently Asked Questions

### What is the required format for the YAML project configuration?

Each project entry in the YAML file must specify a unique `slug` identifier, a human-readable `name`, and a relative `path` pointing to the project directory under an approved root folder. The `load_project_registry` function validates these fields during startup and raises errors for missing attributes or duplicate slugs.

### How does the bot handle renamed or closed Telegram topics?

During the synchronization process, the `_sync_existing_mapping` method checks each stored topic mapping. If a topic was manually closed in Telegram, the bot reopens it automatically; if the project's display name changed in the YAML configuration, the bot renames the corresponding forum topic to maintain consistency.

### Can multiple projects share the same Telegram forum topic?

No. The architecture enforces a strict one-to-one mapping between project slugs and forum topics. The `ProjectThreadManager` creates a unique topic for each project during synchronization, and the `resolve_project` method assumes exclusive ownership of a thread ID by a single project.

### Where are the topic-to-project mappings stored?

The mappings persist in a SQLite database table managed by `ProjectThreadRepository`. Each `ProjectThreadModel` record stores the `chat_id`, `message_thread_id`, and associated `project_slug`, enabling the bot to resolve project contexts across restarts without relying on Telegram topic titles or ephemeral state.