How to Configure Project Threads Mode for Effective Telegram Topic Routing

Enable project threads mode by setting ENABLE_PROJECT_THREADS=true and defining PROJECT_THREADS_MODE as either private or group, then provide a PROJECTS_CONFIG_PATH YAML file that maps project slugs to directory paths.

The claude-code-telegram bot supports advanced conversation routing through project-specific Telegram forum topics. By configuring project threads mode, developers can isolate discussions per codebase directory, ensuring that messages about the API service land in a distinct topic from those about the web frontend. This guide walks through the complete configuration based on the RichardAtCT/claude-code-telegram source code.

Prerequisites and Environment Setup

Required Environment Variables

The thread-routing subsystem is controlled through Pydantic settings defined in src/config/settings.py (lines 200–228). To activate the feature, export the following variables or add them to a .env file:

ENABLE_PROJECT_THREADS=true
PROJECT_THREADS_MODE=group
PROJECT_THREADS_CHAT_ID=-1001234567890
PROJECTS_CONFIG_PATH=/home/bot/projects.yaml
  • ENABLE_PROJECT_THREADS: Boolean flag that activates the entire subsystem.
  • PROJECT_THREADS_MODE: Must be either private (creates topics in private chats) or group (creates forum topics inside a specified group).
  • PROJECT_THREADS_CHAT_ID: Required only when mode is group; specifies the Telegram forum chat ID.
  • PROJECTS_CONFIG_PATH: Absolute path to the YAML file listing projects.

The Settings class validates cross-field dependencies (lines 216–228), raising ValueError if project_threads_chat_id is missing in group mode or if projects_config_path is absent when threading is enabled.

Defining Projects in the Registry

YAML Configuration Schema

Create the YAML file referenced by PROJECTS_CONFIG_PATH. The schema is validated by load_project_registry in src/projects/registry.py (lines 42–66):

projects:
  - slug: api
    name: API Service
    path: api
    enabled: true
  - slug: web
    name: Web Frontend
    path: web
    enabled: true
  - slug: utils
    name: Utility Library
    path: utils
    enabled: false

Each entry requires:

  • slug: Unique identifier used internally for routing.
  • name: Human-readable label used as the Telegram topic title.
  • path: Directory path relative to the approved root.
  • enabled: Boolean controlling whether the project receives a topic.

Path Validation Rules

The registry loader enforces security constraints (lines 81–99 in src/projects/registry.py):

  • Paths must be relative; absolute paths raise ValueError.
  • Resolved paths must remain within the approved_directory to prevent directory traversal.
  • The directory must exist on disk.

Initializing the Thread Manager

Wire the components during bot startup. The ProjectThreadManager class in src/projects/thread_manager.py orchestrates topic lifecycle management:

from src.config.loader import load_settings
from src.projects.registry import load_project_registry
from src.projects.thread_manager import ProjectThreadManager
from src.storage.repositories import ProjectThreadRepository

settings = load_settings()
registry = load_project_registry(
    config_path=settings.projects_config_path,
    approved_directory=settings.approved_directory,
)
repo = ProjectThreadRepository()
thread_manager = ProjectThreadManager(registry, repo)

# Expose to handlers via bot_data

app.bot_data["thread_manager"] = thread_manager
app.bot_data["settings"] = settings

The ProjectThreadRepository (defined in src/storage/repositories.py) persists mappings between project slugs and Telegram message_thread_id values using SQLAlchemy models from src/storage/models.py.

Synchronizing Telegram Topics

The Sync Process

Invoke sync_topics to reconcile the Telegram forum state with the project registry. This method (lines 45–84 in src/projects/thread_manager.py) performs four operations:

  1. Creation: For each enabled project without an existing mapping, calls bot.create_forum_topic and stores the returned message_thread_id.
  2. Reuse: Existing valid mappings are preserved to maintain conversation history.
  3. Renaming: If a project's name changed in the YAML but the topic exists, updates the topic title via bot.edit_forum_topic.
  4. Closure: Identifies stale mappings (projects disabled or removed) and closes those topics.

Handling Stale Topics

The manager queries stale mappings through repository.list_stale_active_mappings (referenced at lines 97–100) and closes them gracefully:


# Simplified excerpt from thread_manager.py lines 97-102

stale_mappings = await self.repository.list_stale_active_mappings(
    chat_id=chat_id,
    active_project_slugs=active_slugs,
)
for stale in stale_mappings:
    await bot.close_forum_topic(chat_id=chat_id, message_thread_id=stale.thread_id)

The implementation handles PrivateTopicsUnavailableError for private chat topics that may become inaccessible.

Storage and Persistence

Topic mappings are stored in the ProjectThreadModel SQLAlchemy class (defined in src/storage/models.py). The repository layer in src/storage/repositories.py provides async CRUD operations:

  • get_by_project_and_chat: Retrieves existing thread mapping.
  • create_mapping: Stores new topic associations.
  • list_stale_active_mappings: Finds orphaned mappings for cleanup.

This persistence ensures that bot restarts maintain consistent topic routing without recreating existing conversations.

Summary

  • Enable the subsystem by setting ENABLE_PROJECT_THREADS=true and choosing a PROJECT_THREADS_MODE (private or group).
  • Validate dependencies through the Pydantic Settings class in src/config/settings.py, which enforces required fields like PROJECT_THREADS_CHAT_ID for group mode.
  • Define projects in a YAML file loaded by src/projects/registry.py, ensuring paths are relative and contained within the approved directory.
  • Initialize the manager by wiring ProjectThreadManager with a ProjectThreadRepository during bot startup.
  • Synchronize topics via sync_topics to create, rename, and close forum topics based on the current project registry state.

Frequently Asked Questions

What is the difference between private and group thread modes?

Private mode creates individual topics within private chats between the bot and each user, suitable for personal project management. Group mode creates forum topics inside a designated Telegram group chat (supergroup with forums enabled), allowing team-wide project discussions. Group mode requires the PROJECT_THREADS_CHAT_ID environment variable, while private mode uses the incoming chat ID dynamically.

How does the bot handle renamed or deleted projects?

The ProjectThreadManager.sync_topics method in src/projects/thread_manager.py detects configuration drift by comparing the active project registry against persisted mappings. When a project is renamed, the manager calls bot.edit_forum_topic to update the topic title. When a project is disabled or removed, the manager identifies stale mappings via list_stale_active_mappings and closes those topics using bot.close_forum_topic, ensuring no orphaned conversations remain.

Can I use absolute paths in the projects.yaml file?

No, absolute paths are explicitly rejected by the validation logic in src/projects/registry.py (lines 81–83). The loader requires relative paths to prevent directory traversal attacks and ensure all project directories remain within the APPROVED_DIRECTORY root. If an absolute path is provided, the bot raises a ValueError during startup, preventing the registry from loading.

What happens if the forum chat ID is invalid?

If PROJECT_THREADS_MODE is set to group but PROJECT_THREADS_CHAT_ID is missing, the Pydantic validator in src/config/settings.py (lines 216–224) raises a ValueError immediately upon settings load, preventing the bot from starting. If the chat ID is provided but invalid (e.g., the bot lacks permissions or the chat doesn't exist), Telegram API calls like create_forum_topic will raise a TelegramError during the sync_topics execution, which the handler should catch and log appropriately.

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 →