@robojs/roadmap
Sync project roadmaps from Jira (or custom providers) to organized Discord forum channels, automatically creating and updating forum threads for each card while providing slash commands for seamless team collaboration.
➞ 📚 Documentation: Getting started
➞ 🚀 Community: Join our Discord server
Features
- 🔄 Automatic bidirectional sync between provider and Discord
- 📋 Forum channels organized by workflow columns
- 🏷️ Smart label tagging with autocomplete (up to 5 tags per thread)
- 👥 Role-based authorization for card creation
- 🔒 Public/private forum access modes
- 🔍 Autocomplete for cards, columns, labels, and issue types
- 📅 Date range filtering for programmatic queries
- 🌐 Optional REST API for external integrations
- 🔌 Extensible provider architecture (Jira, GitHub, Linear, custom)
- ⚡ Idempotent sync with automatic error recovery
Quick Happy Path
npx robo add @robojs/roadmapin an existing Robo project.- Set Jira env vars (
JIRA_URL,JIRA_EMAIL,JIRA_API_TOKEN,JIRA_PROJECT_KEY) in your.env. - Start Robo with
npx robo dev. - In Discord, run
/roadmap setupto create roadmap forums. - Run
/roadmap syncto import cards. - Click into the generated roadmap forums to collaborate on cards.
For a full walkthrough, see Getting Started and Provider Setup.
Getting Started
npx robo add @robojs/roadmapNew to Robo.js? Start your project with this plugin pre-installed:
npx create-robo <project-name> -p @robojs/roadmapSet Jira environment variables (
JIRA_URL,JIRA_EMAIL,JIRA_API_TOKEN,JIRA_PROJECT_KEY) for the plugin to function. See Provider Setup for details.
💡 Install
@robojs/serverto enable REST API features for external integrations.
Quick Start
-
Configure Jira credentials in your
.envfile:JIRA_URL="https://company.atlassian.net"
JIRA_EMAIL="[email protected]"
JIRA_API_TOKEN="your-api-token"
JIRA_PROJECT_KEY="PROJ" -
Start your Robo:
Terminalnpx robo dev -
Run
/roadmap setupin Discord to create forum channels -
Run
/roadmap syncto sync your cards from Jira -
Cards appear as forum threads organized by column (Backlog, In Progress, Done, etc.)
During setup, the plugin creates a dedicated category with forum channels for each workflow column. Each card becomes a forum thread in its respective column channel, complete with labels, descriptions, and automatic updates.
Discord Commands
The plugin provides four slash commands for managing your roadmap:
/roadmap setup
Creates the roadmap forum structure. Admin only.
- Creates a category with forum channels for each workflow column
- Shows interactive setup message with access toggle and role selector
- Configures authorized creator roles
Example:
/roadmap setup
/roadmap sync
Manually syncs cards from provider to Discord. Admin only.
-
Options:
dry-run(boolean) - Preview sync without making changes
-
Shows sync statistics (created, updated, archived)
-
Cancellation: A cancel button appears during sync, allowing you to stop the operation. Only administrators or the user who started the sync can cancel. Partial results are preserved and shown (e.g., "Partial sync: 15/42 cards processed").
-
Idempotent: safe to run multiple times
-
When a card's column changes, the thread is moved to the new forum channel. The old thread is locked and archived to preserve discussion history, and the new thread links to it if there were user messages.
Example:
/roadmap sync dry-run:true
/roadmap add
Creates a new card. Authorized users.
-
Options:
title(required) - Card titledescription- Card descriptioncolumn(autocomplete) - Target columnlabels(autocomplete, comma-separated) - Card labelsissue-type(autocomplete) - Issue type (Task, Bug, etc.)
-
Automatically syncs to Discord after creation
-
Supports autocomplete for columns, labels, and issue types
Example:
/roadmap add title:"Add dark mode" column:"Backlog" labels:"feature,ui"
/roadmap edit
Updates an existing card. Authorized users.
-
Options:
card(autocomplete, required) - Card to edittitle- New titledescription- New descriptioncolumn(autocomplete) - Move to columnlabels(autocomplete, comma-separated) - Update labels
-
Updates both provider and Discord thread
-
Autocomplete searches cards by title and key
-
When changing a card's column, the Discord thread is moved to the new forum channel. The previous thread is locked and archived, and the new thread includes a link to the old discussion if it had user activity.
Example:
/roadmap edit card:"PROJ-123" column:"In Progress"
💡 All autocomplete fields are cached for performance and refresh every 5 minutes by default. You can change this by setting
autocompleteCacheTtl(in milliseconds) in your roadmap plugin options; lower values refresh suggestions more often at the cost of more provider API calls. Labels support comma-separated values for multi-selection.
Provider Setup
Jira Provider
Jira is the built-in provider for syncing roadmaps from Jira Cloud.
Required Environment Variables:
JIRA_URL- Your Jira instance URL (e.g.,https://company.atlassian.net)JIRA_EMAIL- Email address for authenticationJIRA_API_TOKEN- API token for authentication (create one here)JIRA_PROJECT_KEY- Project key to sync (e.g.,PROJ)
Optional Environment Variables:
JIRA_JQL- Custom JQL query to filter issuesJIRA_MAX_RESULTS- Max results per page (1-1000, default 100)JIRA_DEFAULT_ISSUE_TYPE- Default issue type for new cards (default 'Task')JIRA_DISCORD_USER_ID_FIELD_ID- Custom field ID containing Discord User ID (e.g.,customfield_10001)
Example .env file:
JIRA_URL="https://company.atlassian.net"
JIRA_EMAIL="[email protected]"
JIRA_API_TOKEN="ATATT3xFfGF0..."
JIRA_PROJECT_KEY="PROJ"
JIRA_JQL="labels = public"
JIRA_MAX_RESULTS="50"
JIRA_DEFAULT_ISSUE_TYPE="Story"
JIRA_DISCORD_USER_ID_FIELD_ID="customfield_10001"
Configuration File Example:
// config/plugins/robojs/roadmap.mjs
export default {
provider: {
type: 'jira',
options: {
url: process.env.JIRA_URL,
email: process.env.JIRA_EMAIL,
apiToken: process.env.JIRA_API_TOKEN,
projectKey: process.env.JIRA_PROJECT_KEY,
jql: 'labels = public',
maxResults: 50
}
}
}
JQL Filtering Examples:
// Public cards only
jql: 'project = PROJ AND labels = public'
// Exclude private cards
jql: 'project = PROJ AND labels != private'
// Active cards only (not resolved)
jql: 'project = PROJ AND resolution = Unresolved'
// Cards updated in last 30 days
jql: 'project = PROJ AND updated >= -30d'
💡 JQL (Jira Query Language) allows powerful filtering. See Atlassian JQL documentation for advanced queries.
Discord User ID Custom Field
You can use a Jira custom field to directly assign Discord users to cards, bypassing the standard Jira assignee mapping. This is useful when you want to assign cards to Discord users who may not have Jira accounts or when you want to override the Jira assignee.
Setup:
- Create a text custom field in Jira (e.g.,
customfield_10001) - Configure the field ID in your environment or config:
JIRA_DISCORD_USER_ID_FIELD_ID="customfield_10001" - In Jira issues, enter the Discord User ID (17-19 digit numeric string) in this custom field
Behavior:
- Priority: Custom field Discord User ID takes priority over Jira assignee mapping
- Fallback: If the custom field is empty or invalid, falls back to Jira assignee mapping
- Avatar: When a custom field Discord User ID is present, that user's Discord avatar is displayed instead of the Jira assignee's avatar
- Validation: Invalid Discord User ID formats (not 17-19 digits) are logged and ignored, falling back to Jira assignee
Example:
provider: new JiraProvider({
type: 'jira',
options: {
// ... credentials ...
discordUserIdFieldId: 'customfield_10001'
}
})
[!NOTE] Configuration precedence: explicit config file > plugin options > environment variables. Environment variables provide the simplest setup for most use cases.
️ Configuration
Column Mapping
✅ Default Behavior (Works Out of the Box):
The plugin automatically maps Jira status categories to three columns without any configuration:
- "To Do" category → "Backlog" column
- "In Progress" category → "In Progress" column
- "Done" category → "Done" column
These defaults are always active - you can start syncing immediately without configuring anything!
🔧 Optional Customization:
If your Jira workflow uses different statuses or you want custom columns, you can override defaults at two levels:
Provider-Level (Default): Configure custom columns and status mappings in your provider config:
provider: new JiraProvider({
type: 'jira',
options: {
// ... credentials ...
columnConfig: {
columns: [
{ id: 'planning', name: 'Planning', order: 0 },
{ id: 'development', name: 'Development', order: 1 },
{ id: 'review', name: 'Review', order: 2 },
{ id: 'done', name: 'Done', order: 3, archived: true }
],
statusMapping: {
'Backlog': 'Planning',
'To Do': 'Planning',
'In Progress': 'Development',
'Code Review': 'Review',
'QA': 'Review',
'Done': 'Done',
'Closed': null, // Track but don't create forum thread
'Won\'t Do': null
}
}
}
})
Runtime-Level (Per-Guild): Override mappings per-guild using /roadmap setup or the settings API:
import { setColumnMapping } from '@robojs/roadmap'
// Map QA status to Development column
setColumnMapping(guildId, 'QA', 'Development')
// Track Blocked status without creating forum thread
setColumnMapping(guildId, 'Blocked', null)
Many-to-One Support: Multiple statuses can map to the same column (e.g., both "Code Review" and "QA" map to "Review").
Track-Only Mappings: Map a status to null to track it programmatically (useful for changelogs) without creating a Discord forum thread.
Plugin Options
Configure the plugin in config/plugins/robojs/roadmap.mjs (or .ts):
import { JiraProvider } from '@robojs/roadmap'
export default {
// Provider instance or config object (required)
provider: new JiraProvider({
type: 'jira',
options: {
url: process.env.JIRA_URL,
email: process.env.JIRA_EMAIL,
apiToken: process.env.JIRA_API_TOKEN,
projectKey: process.env.JIRA_PROJECT_KEY,
jql: 'labels = public',
maxResults: 100,
defaultIssueType: 'Task'
}
}),
// Cache duration for autocomplete suggestions (default: 300000 = 5 minutes)
autocompleteCacheTtl: 300000,
// Whether /roadmap add and /roadmap edit replies are ephemeral (true) or visible in-channel (false)
ephemeralCommands: true,
// Default template for Discord thread titles (can be overridden per-guild)
threadTitleTemplate: "[{id}] {title}",
// Reserved for future automatic sync feature
autoSync: false,
// Reserved for future sync interval configuration
syncInterval: null
}
Available Options:
| Option | Type | Default | Description |
|---|---|---|---|
provider | RoadmapProvider | ProviderConfig | required | Provider instance or config object |
autocompleteCacheTtl | number | 300000 | Cache duration in milliseconds for autocomplete |
ephemeralCommands | boolean | true | Controls whether /roadmap add and /roadmap edit replies are ephemeral |
threadTitleTemplate | string | undefined | Default template for formatting Discord thread titles (see Thread Title Templates) |
autoSync | boolean | false | Reserved for future automatic sync |
syncInterval | number | null | null | Reserved for future sync interval |
Jira Configuration
Jira-Specific Options:
| Option | Type | Required | Description |
|---|---|---|---|
url | string | yes | Jira instance URL |
email | string | yes | Email for authentication |
apiToken | string | yes | API token for authentication |
projectKey | string | yes | Project key to sync |
jql | string | no | Custom JQL query to filter issues |
maxResults | number | no | Max results per page (1-1000, default 100) |
defaultIssueType | string | no | Default issue type (default 'Task') |
Example with JQL Filtering:
export default {
provider: {
type: 'jira',
options: {
url: 'https://company.atlassian.net',
email: '[email protected]',
apiToken: process.env.JIRA_API_TOKEN,
projectKey: 'PROJ',
jql: 'project = PROJ AND labels = public AND resolution = Unresolved',
maxResults: 50,
defaultIssueType: 'Story'
}
}
}
Authorization & Permissions
Permission Levels
Admin Commands (/roadmap setup, /roadmap sync):
- Require Administrator permission in Discord
Authorized User Commands (/roadmap add, /roadmap edit):
- Require admin OR authorized creator role
- Configure authorized roles via
/roadmap setupinteractive message or settings API
Required Discord Bot Permissions
| Permission | Required For |
|---|---|
| View Channels | See forum channels |
| Create Public Threads | Create card threads |
| Send Messages in Threads | Post card content |
| Manage Threads | Update and archive threads |
| Manage Messages | Edit thread starter messages |
💡 Permissions inherit from category to forum channels. Set permissions on the roadmap category for easiest management.
Public vs Private Forums
- Public mode:
@everyonecan view forum channels - Private mode: Only authorized roles can view forum channels
Configure access mode via /roadmap setup or the settings API.
️ Thread Title Templates
Customize how Discord thread titles are formatted using template strings with placeholders.
Template Format
Templates support two placeholders:
{id}- The card ID (e.g., "ROBO-23", "PROJ-123"){title}- The card title
Configuration
Global Default (Plugin Options):
Set a default template for all guilds in your plugin configuration:
// config/plugins/robojs/roadmap.ts
export default {
provider: { /* ... */ },
threadTitleTemplate: "[{id}] {title}" // Default for all guilds
}
Per-Guild Override (Settings API):
Override the global default per-guild using the settings API:
import { updateSettings } from '@robojs/roadmap'
// Set custom template for a specific guild
updateSettings(guildId, {
threadTitleTemplate: "{id} - {title}"
})
Examples
// Default format: "[ROBO-23] Lorem ipsum"
threadTitleTemplate: "[{id}] {title}"
// Alternative format: "ROBO-23 - Lorem ipsum"
threadTitleTemplate: "{id} - {title}"
// Title first: "Lorem ipsum (ROBO-23)"
threadTitleTemplate: "{title} ({id})"
// Just ID: "ROBO-23"
threadTitleTemplate: "{id}"
// Just title (same as undefined/empty)
threadTitleTemplate: "{title}"
Behavior
- Character Limit: Discord thread names have a 100 character limit. The template system automatically truncates the
{title}portion while preserving the template structure (including{id}). - Fallback: If no template is provided (or it's empty), thread titles use just the card title.
- Precedence: Guild-specific settings override the global plugin default.
- Sync Updates: Running
/roadmap syncwill update all existing thread titles to match the current template configuration.
Use Cases
- Consistent formatting: Ensure all threads follow your team's naming convention
- Easy identification: Include card IDs in thread titles for quick reference
- Custom branding: Format titles to match your organization's style guide
Programmatic API
All functionality is available as importable functions for use in custom commands, events, or plugins.
Provider Access
import { getProvider, isProviderReady } from '@robojs/roadmap'
// Check if provider is ready before use
if (isProviderReady()) {
const provider = getProvider()
const cards = await provider.fetchCards()
}
Sync Operations
import { syncRoadmap } from '@robojs/roadmap'
// Full sync
const result = await syncRoadmap({
guild,
provider
})
// Dry run (preview without changes)
const preview = await syncRoadmap({
guild,
provider,
dryRun: true
})
console.log(`Synced ${result.stats.total} cards`)
Thread Movement on Column Changes
When a card moves to a different column (e.g., from "Backlog" to "In Progress"), the sync engine automatically moves the Discord thread to the corresponding forum channel:
- New thread created in the target forum with the same name, tags, and content
- Old thread locked and archived to preserve discussion history
- Conditional linking: If the old thread had user messages (more than just the starter message), the new thread includes a link at the top: "📜 See X messages in previous discussion: [link]"
- Thread history tracked in settings for audit trail
This behavior applies to:
/roadmap syncoperations/roadmap editcommand when changing the column- REST API
PUT /api/roadmap/cards/:guildId/:cardIdwhen updating the column
Example:
import { syncRoadmap } from '@robojs/roadmap'
// When a card moves from "Backlog" to "In Progress"
// The thread will be moved to the In Progress forum
const result = await syncRoadmap({ guild, provider })
// Old thread: locked and archived in Backlog forum
// New thread: active in In Progress forum with link to old thread
Card Operations
import { getProvider } from '@robojs/roadmap'
import type { CreateCardInput, UpdateCardInput } from '@robojs/roadmap'
const provider = getProvider()
// Create a card
const newCard: CreateCardInput = {
title: 'Add dark mode',
description: 'Implement dark theme support',
column: 'Backlog',
labels: ['feature', 'ui']
}
const result = await provider.createCard(newCard)
// Update a card
const cardId = 'PROJ-123'
const update: UpdateCardInput = {
title: 'Add dark mode support',
column: 'In Progress',
labels: ['feature', 'ui', 'high-priority']
}
await provider.updateCard(cardId, update)
Settings Management
import { getSettings, updateSettings, canUserCreateCards } from '@robojs/roadmap'
import { PermissionFlagsBits } from 'discord.js'
// Get current settings
const settings = getSettings(guildId)
// Update settings
updateSettings(guildId, { isPublic: true })
// Check authorization
const userRoleIds = member.roles.cache.map((r) => r.id)
const isAdmin = member.permissions.has(PermissionFlagsBits.Administrator)
if (canUserCreateCards(guildId, userRoleIds, isAdmin)) {
// User can create cards
}
Forum Management
import { createOrGetRoadmapCategory, toggleForumAccess, updateForumTagsForColumn } from '@robojs/roadmap'
// Create forum structure
const { category, forums } = await createOrGetRoadmapCategory({
guild,
columns
})
// Toggle access mode
await toggleForumAccess(guild, 'public')
// Update tags for a column
const columnName = 'Backlog'
const tagNames = ['feature', 'bug', 'enhancement', 'docs', 'design']
await updateForumTagsForColumn(guild, columnName, tagNames)
[!NOTE] For complete API reference with all parameters, types, and return values, see the auto-generated documentation.
Date Range Filtering
Fetch cards by time range for reports, analytics, or incremental syncs.
Helper Functions
import {
getCardsFromLastMonth,
getCardsFromLastWeek,
getCardsFromLastDays,
getCardsFromDateRange
} from '@robojs/roadmap'
// Last calendar month
const lastMonth = await getCardsFromLastMonth(provider)
// Last 7 days
const lastWeek = await getCardsFromLastWeek(provider)
// Last 30 days
const last30Days = await getCardsFromLastDays(provider, 30)
// Custom date range
const cards = await getCardsFromDateRange(provider, new Date('2024-01-01'), new Date('2024-01-31'))
// Use 'created' instead of 'updated' (default)
const created = await getCardsFromLastWeek(provider, 'created')
Use Cases
- Generate reports: Monthly activity summaries
- Track recent activity: Cards updated in last 24 hours
- Incremental syncs: Only sync cards changed since last run
- Analytics: Trend analysis over time
[!NOTE] Date filtering requires provider support. Jira provider fully supports all date range queries via
fetchCardsByDateRange().
REST API (Optional)
💡 Install
@robojs/serverto enable REST endpoints:npx robo add @robojs/server
The REST API provides HTTP endpoints for external integrations, custom dashboards, and automation.
Response Format
Success:
{
"success": true,
"data": {
/* endpoint-specific data */
}
}
Error:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"hint": "Optional operator-friendly hint with a suggested fix"
}
}
Common Error Codes: PROVIDER_NOT_READY, FORUM_NOT_SETUP, GUILD_NOT_FOUND, INVALID_REQUEST, SYNC_FAILED
Key Endpoints
Health & Provider
# Check health status
curl http://localhost:3000/api/roadmap/health
# Get provider info
curl http://localhost:3000/api/roadmap/provider/infoForum Management
# Create forum structure
curl -X POST http://localhost:3000/api/roadmap/forum/:guildId/setup
# Toggle access mode
curl -X PUT http://localhost:3000/api/roadmap/forum/:guildId/access -H "Content-Type: application/json" -d '{"mode": "public"}'Sync Operations
# Trigger sync
curl -X POST http://localhost:3000/api/roadmap/sync/:guildId
# Dry run
curl -X POST "http://localhost:3000/api/roadmap/sync/:guildId?dryRun=true"
# Get sync status
curl http://localhost:3000/api/roadmap/sync/:guildId/statusCard Management
# Create card
curl -X POST http://localhost:3000/api/roadmap/cards/:guildId -H "Content-Type: application/json" -d '{"title": "New feature", "column": "Backlog"}'
# Get card details
curl http://localhost:3000/api/roadmap/cards/:guildId/:cardId⚠️ Security Warning: The card creation endpoint lacks authentication by default. Implement authentication middleware before exposing to the internet.
Use Cases
- Custom dashboards: Monitor roadmap sync status across multiple guilds
- External automation: Trigger syncs from CI/CD pipelines or webhooks
- Monitoring tools: Track provider health and sync statistics
- Multi-guild management: Manage roadmap configuration across servers
Custom Providers
Extend the roadmap to sync from GitHub Projects, Linear, or any custom backend.
Minimal implementation checklist
- Extend
RoadmapProviderwith your own provider class. - Implement the required methods:
fetchCards,getColumns,getCard,createCard,updateCard, andgetProviderInfo. - Optionally implement helpers like
getIssueTypes,getLabels,fetchCardsByDateRange,validateConfig, andinitif your backend supports them. - Register your provider in
config/plugins/robojs/roadmap.(mjs\|ts)via the plugin options.
Creating a Provider
import { RoadmapProvider } from '@robojs/roadmap'
import type {
RoadmapCard,
RoadmapColumn,
ProviderInfo,
CreateCardInput,
UpdateCardInput,
CreateCardResult,
UpdateCardResult
} from '@robojs/roadmap'
class CustomProvider extends RoadmapProvider {
// Required: Fetch all cards
async fetchCards(): Promise<RoadmapCard[]> {
// Implement your logic
return []
}
// Required: Get workflow columns
async getColumns(): Promise<RoadmapColumn[]> {
return []
}
// Required: Get single card by ID
async getCard(id: string): Promise<RoadmapCard | null> {
return null
}
// Required: Create a new card
async createCard(input: CreateCardInput): Promise<CreateCardResult> {
// TODO: Implement card creation logic
// Return placeholder for demonstration
const card: RoadmapCard = {
id: 'placeholder',
title: input.title,
description: input.description,
column: input.column,
labels: input.labels || [],
assignees: input.assignees || [],
url: 'https://example.com',
updatedAt: new Date()
}
return { card, success: false, message: 'Not implemented' }
}
// Required: Update existing card (partial update - only provided fields are changed)
async updateCard(cardId: string, input: UpdateCardInput): Promise<UpdateCardResult> {
// TODO: Implement card update logic
// Return placeholder for demonstration
const card: RoadmapCard = {
id: cardId,
title: input.title || 'Unknown',
description: input.description || '',
column: input.column || 'Unknown',
labels: input.labels || [],
assignees: input.assignees || [],
url: 'https://example.com',
updatedAt: new Date()
}
return { card, success: false, message: 'Not implemented' }
}
// Required: Provider metadata
async getProviderInfo(): Promise<ProviderInfo> {
return {
name: 'Custom Provider',
version: '1.0.0',
capabilities: ['cards', 'columns']
}
}
// Optional: Validate configuration
async validateConfig(): Promise<void> {
// Throw error if config is invalid
}
// Optional: Initialize provider
async init(): Promise<void> {
// Setup connections, auth, etc.
}
// Optional: Get issue types
async getIssueTypes(): Promise<string[]> {
return ['Task', 'Bug', 'Feature']
}
// Optional: Get labels
async getLabels(): Promise<string[]> {
return []
}
// Optional: Date range filtering
async fetchCardsByDateRange(
startDate: Date,
endDate: Date,
dateField?: 'created' | 'updated'
): Promise<RoadmapCard[]> {
return []
}
}
Registering a Custom Provider
// config/plugins/robojs/roadmap.mjs
import { CustomProvider } from './providers/custom.js'
export default {
provider: new CustomProvider({
// Your provider config
})
}
💡 See
AGENTS.mdfor detailed implementation guidance, best practices, and architecture details.
️ Troubleshooting
Provider not configured
- Check environment variables are set correctly
- Verify Jira URL is accessible
- Ensure API token has required permissions
Authentication failed
- Verify Jira email and API token
- Ensure token hasn't expired
- Check token has project access
Missing permissions
- Ensure bot has required Discord permissions (see Authorization & Permissions)
- Check permissions inherit from category to forums
Sync errors (content too long)
- Card descriptions are automatically truncated to Discord's limits
- 2000 characters for forum thread starter messages and message edits
Cards not appearing
- Run
/roadmap setupfirst to create forum channels - Check that provider columns match configured columns
- Verify JQL query doesn't exclude all cards
Rate limit errors
- Reduce
maxResultssetting - Increase time between syncs
- Wait before retrying
Forum not set up
- Run
/roadmap setupbefore using other commands - Check bot has permissions to create channels
Autocomplete not working
- Wait for cache refresh (default 5 minutes)
- Restart bot to force cache refresh
- Check provider is ready via
isProviderReady()
How to cancel a running sync
- Click the "Cancel Sync" button that appears during the sync progress.
- Only administrators or the user who started the sync can cancel.
- The sync stops after finishing the current card (typically < 1 second).
- Partial results are preserved and shown in the cancellation message.
- Stats reflect cards successfully processed before cancellation.
Sync canceled but shows partial results
- This is expected behavior — cancellation preserves completed work.
- The cancellation message shows "Partial sync: X/Y cards processed".
- Stats (created, updated, archived, errors) reflect processed cards before cancellation.
- Cards synced before cancellation remain in Discord.
- Run
/roadmap syncagain to complete the sync.
Cancel button not working
- Verify you are an administrator or the user who started the sync.
- If the sync already completed, the cancel button won't work (sync already ended).
- If you see a "Not authorized" message, you don't have permission to cancel this sync.
- If the button appears disabled, the sync may have already finished.
- Check bot logs for any errors related to cancellation.
Threads moved to different forums
- This is expected behavior when a card's column changes
- Old threads are locked and archived to preserve history
- New threads link to old discussions if they had user activity
- Thread history is tracked in settings
Old threads are locked
- Threads are automatically locked when a card moves to a new column
- This prevents confusion from discussions in multiple places
- Users can still view locked threads and their history
- The new thread includes a link to the old discussion
Thread links show "See X messages"
- Links only appear when the old thread had user messages (not just the starter message)
- Message count excludes the starter message (e.g., "See 5 messages" means 5 user replies)
- This helps users find valuable prior discussions
- Threads with only the starter message are not linked
Thread creation fails
- Check bot has Create Public Threads permission
- Verify forum channels exist
- Ensure forum isn't at Discord's thread limit
💡 Enable debug logging by setting
ROBO_LOG_LEVEL=debugin your.envfile. Look for logs withroadmap:prefix for detailed diagnostics.
[!HELP] Still stuck? Enable debug logging and check for errors in the console. If you need further assistance, join our Discord community.
Got questions?
If you need help, hop into our community Discord. We love to chat, and Sage (our resident AI Robo) is always online to assist.
➞ 🚀 Community: Join our Discord server