Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot]
1c5d5651a9 docs: auto-update documentation based on changes in next branch
This PR was automatically generated to update documentation based on recent changes.

  Original commit: chore: apply requested coderabbit changes\n\n

  Co-authored-by: Claude <claude-assistant@anthropic.com>
2025-10-16 17:31:18 +00:00
23 changed files with 322 additions and 680 deletions

View File

@@ -15,7 +15,6 @@ import {
} from '@tm/core/auth';
import * as ui from '../utils/ui.js';
import { ContextCommand } from './context.command.js';
import { displayError } from '../utils/error-handler.js';
/**
* Result type from auth command
@@ -118,7 +117,8 @@ export class AuthCommand extends Command {
process.exit(0);
}, 100);
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -134,7 +134,8 @@ export class AuthCommand extends Command {
process.exit(1);
}
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -146,7 +147,8 @@ export class AuthCommand extends Command {
const result = this.displayStatus();
this.setLastResult(result);
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -162,7 +164,8 @@ export class AuthCommand extends Command {
process.exit(1);
}
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -387,7 +390,7 @@ export class AuthCommand extends Command {
message: 'Authentication successful'
};
} catch (error) {
displayError(error, { skipExit: true });
this.handleAuthError(error as AuthenticationError);
return {
success: false,
@@ -450,6 +453,51 @@ export class AuthCommand extends Command {
}
}
/**
* Handle authentication errors
*/
private handleAuthError(error: AuthenticationError): void {
console.error(chalk.red(`\n✗ ${error.message}`));
switch (error.code) {
case 'NETWORK_ERROR':
ui.displayWarning(
'Please check your internet connection and try again.'
);
break;
case 'INVALID_CREDENTIALS':
ui.displayWarning('Please check your credentials and try again.');
break;
case 'AUTH_EXPIRED':
ui.displayWarning(
'Your session has expired. Please authenticate again.'
);
break;
default:
if (process.env.DEBUG) {
console.error(chalk.gray(error.stack || ''));
}
}
}
/**
* Handle general errors
*/
private handleError(error: any): void {
if (error instanceof AuthenticationError) {
this.handleAuthError(error);
} else {
const msg = error?.getSanitizedDetails?.() ?? {
message: error?.message ?? String(error)
};
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
if (error.stack && process.env.DEBUG) {
console.error(chalk.gray(error.stack));
}
}
}
/**
* Set the last result for programmatic access
*/

View File

@@ -8,9 +8,12 @@ import chalk from 'chalk';
import inquirer from 'inquirer';
import search from '@inquirer/search';
import ora, { Ora } from 'ora';
import { AuthManager, type UserContext } from '@tm/core/auth';
import {
AuthManager,
AuthenticationError,
type UserContext
} from '@tm/core/auth';
import * as ui from '../utils/ui.js';
import { displayError } from '../utils/error-handler.js';
/**
* Result type from context command
@@ -116,7 +119,8 @@ export class ContextCommand extends Command {
const result = this.displayContext();
this.setLastResult(result);
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -212,7 +216,8 @@ export class ContextCommand extends Command {
process.exit(1);
}
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -253,7 +258,6 @@ export class ContextCommand extends Command {
this.authManager.updateContext({
orgId: selectedOrg.id,
orgName: selectedOrg.name,
orgSlug: selectedOrg.slug,
// Clear brief when changing org
briefId: undefined,
briefName: undefined
@@ -300,7 +304,8 @@ export class ContextCommand extends Command {
process.exit(1);
}
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -424,7 +429,8 @@ export class ContextCommand extends Command {
process.exit(1);
}
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -470,7 +476,8 @@ export class ContextCommand extends Command {
process.exit(1);
}
} catch (error: any) {
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -506,13 +513,11 @@ export class ContextCommand extends Command {
process.exit(1);
}
// Fetch org to get a friendly name and slug (optional)
// Fetch org to get a friendly name (optional)
let orgName: string | undefined;
let orgSlug: string | undefined;
try {
const org = await this.authManager.getOrganization(brief.accountId);
orgName = org?.name;
orgSlug = org?.slug;
} catch {
// Non-fatal if org lookup fails
}
@@ -523,7 +528,6 @@ export class ContextCommand extends Command {
this.authManager.updateContext({
orgId: brief.accountId,
orgName,
orgSlug,
briefId: brief.id,
briefName
});
@@ -545,7 +549,8 @@ export class ContextCommand extends Command {
try {
if (spinner?.isSpinning) spinner.stop();
} catch {}
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -674,6 +679,26 @@ export class ContextCommand extends Command {
}
}
/**
* Handle errors
*/
private handleError(error: any): void {
if (error instanceof AuthenticationError) {
console.error(chalk.red(`\n✗ ${error.message}`));
if (error.code === 'NOT_AUTHENTICATED') {
ui.displayWarning('Please authenticate first: tm auth login');
}
} else {
const msg = error?.message ?? String(error);
console.error(chalk.red(`Error: ${msg}`));
if (error.stack && process.env.DEBUG) {
console.error(chalk.gray(error.stack));
}
}
}
/**
* Set the last result for programmatic access
*/

View File

@@ -7,10 +7,13 @@ import { Command } from 'commander';
import chalk from 'chalk';
import inquirer from 'inquirer';
import ora, { Ora } from 'ora';
import { AuthManager, type UserContext } from '@tm/core/auth';
import {
AuthManager,
AuthenticationError,
type UserContext
} from '@tm/core/auth';
import { TaskMasterCore, type ExportResult } from '@tm/core';
import * as ui from '../utils/ui.js';
import { displayError } from '../utils/error-handler.js';
/**
* Result type from export command
@@ -194,7 +197,8 @@ export class ExportCommand extends Command {
};
} catch (error: any) {
if (spinner?.isSpinning) spinner.fail('Export failed');
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -330,6 +334,26 @@ export class ExportCommand extends Command {
return confirmed;
}
/**
* Handle errors
*/
private handleError(error: any): void {
if (error instanceof AuthenticationError) {
console.error(chalk.red(`\n✗ ${error.message}`));
if (error.code === 'NOT_AUTHENTICATED') {
ui.displayWarning('Please authenticate first: tm auth login');
}
} else {
const msg = error?.message ?? String(error);
console.error(chalk.red(`Error: ${msg}`));
if (error.stack && process.env.DEBUG) {
console.error(chalk.gray(error.stack));
}
}
}
/**
* Get the last export result (useful for testing)
*/

View File

@@ -17,9 +17,8 @@ import {
} from '@tm/core';
import type { StorageType } from '@tm/core/types';
import * as ui from '../utils/ui.js';
import { displayError } from '../utils/error-handler.js';
import { displayCommandHeader } from '../utils/display-helpers.js';
import {
displayHeader,
displayDashboards,
calculateTaskStatistics,
calculateSubtaskStatistics,
@@ -107,7 +106,14 @@ export class ListTasksCommand extends Command {
this.displayResults(result, options);
}
} catch (error: any) {
displayError(error);
const msg = error?.getSanitizedDetails?.() ?? {
message: error?.message ?? String(error)
};
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
if (error.stack && process.env.DEBUG) {
console.error(chalk.gray(error.stack));
}
process.exit(1);
}
}
@@ -251,12 +257,15 @@ export class ListTasksCommand extends Command {
* Display in text format with tables
*/
private displayText(data: ListTasksResult, withSubtasks?: boolean): void {
const { tasks, tag, storageType } = data;
const { tasks, tag } = data;
// Display header using utility function
displayCommandHeader(this.tmCore, {
// Get file path for display
const filePath = this.tmCore ? `.taskmaster/tasks/tasks.json` : undefined;
// Display header without banner (banner already shown by main CLI)
displayHeader({
tag: tag || 'master',
storageType
filePath: filePath
});
// No tasks message

View File

@@ -9,9 +9,8 @@ import chalk from 'chalk';
import boxen from 'boxen';
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
import type { StorageType } from '@tm/core/types';
import { displayError } from '../utils/error-handler.js';
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
import { displayCommandHeader } from '../utils/display-helpers.js';
import { displayHeader } from '../ui/index.js';
/**
* Options interface for the next command
@@ -59,7 +58,6 @@ export class NextCommand extends Command {
* Execute the next command
*/
private async executeCommand(options: NextCommandOptions): Promise<void> {
let hasError = false;
try {
// Validate options (throws on invalid options)
this.validateOptions(options);
@@ -78,17 +76,16 @@ export class NextCommand extends Command {
this.displayResults(result, options);
}
} catch (error: any) {
hasError = true;
displayError(error, { skipExit: true });
const msg = error?.getSanitizedDetails?.() ?? {
message: error?.message ?? String(error)
};
// Allow error to propagate for library compatibility
throw new Error(msg.message || 'Unexpected error in next command');
} finally {
// Always clean up resources, even on error
await this.cleanup();
}
// Exit after cleanup completes
if (hasError) {
process.exit(1);
}
}
/**
@@ -173,10 +170,9 @@ export class NextCommand extends Command {
* Display in text format
*/
private displayText(result: NextTaskResult): void {
// Display header with storage info
displayCommandHeader(this.tmCore, {
tag: result.tag || 'master',
storageType: result.storageType
// Display header with tag (no file path for next command)
displayHeader({
tag: result.tag || 'master'
});
if (!result.found || !result.task) {
@@ -195,6 +191,7 @@ export class NextCommand extends Command {
}
)
);
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
console.log(
`\n${chalk.dim('Tip: Try')} ${chalk.cyan('task-master list --status pending')} ${chalk.dim('to see all pending tasks')}`
);
@@ -211,6 +208,8 @@ export class NextCommand extends Command {
headerColor: 'green',
showSuggestedActions: true
});
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
}
/**

View File

@@ -12,7 +12,6 @@ import {
type TaskStatus
} from '@tm/core';
import type { StorageType } from '@tm/core/types';
import { displayError } from '../utils/error-handler.js';
/**
* Valid task status values for validation
@@ -86,7 +85,6 @@ export class SetStatusCommand extends Command {
private async executeCommand(
options: SetStatusCommandOptions
): Promise<void> {
let hasError = false;
try {
// Validate required options
if (!options.id) {
@@ -137,15 +135,16 @@ export class SetStatusCommand extends Command {
oldStatus: result.oldStatus,
newStatus: result.newStatus
});
} catch (error: any) {
hasError = true;
if (options.format === 'json') {
const errorMessage = error?.getSanitizedDetails
? error.getSanitizedDetails().message
: error instanceof Error
? error.message
: String(error);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
if (!options.silent) {
console.error(
chalk.red(`Failed to update task ${taskId}: ${errorMessage}`)
);
}
if (options.format === 'json') {
console.log(
JSON.stringify({
success: false,
@@ -154,13 +153,8 @@ export class SetStatusCommand extends Command {
timestamp: new Date().toISOString()
})
);
} else if (!options.silent) {
// Show which task failed with context
console.error(chalk.red(`\nFailed to update task ${taskId}:`));
displayError(error, { skipExit: true });
}
// Don't exit here - let finally block clean up first
break;
process.exit(1);
}
}
@@ -176,26 +170,25 @@ export class SetStatusCommand extends Command {
// Display results
this.displayResults(this.lastResult, options);
} catch (error: any) {
hasError = true;
if (options.format === 'json') {
const errorMessage =
error instanceof Error ? error.message : 'Unknown error occurred';
console.log(JSON.stringify({ success: false, error: errorMessage }));
} else if (!options.silent) {
displayError(error, { skipExit: true });
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Unknown error occurred';
if (!options.silent) {
console.error(chalk.red(`Error: ${errorMessage}`));
}
if (options.format === 'json') {
console.log(JSON.stringify({ success: false, error: errorMessage }));
}
process.exit(1);
} finally {
// Clean up resources
if (this.tmCore) {
await this.tmCore.close();
}
}
// Exit after cleanup completes
if (hasError) {
process.exit(1);
}
}
/**

View File

@@ -9,9 +9,7 @@ import boxen from 'boxen';
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
import type { StorageType } from '@tm/core/types';
import * as ui from '../utils/ui.js';
import { displayError } from '../utils/error-handler.js';
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
import { displayCommandHeader } from '../utils/display-helpers.js';
/**
* Options interface for the show command
@@ -114,7 +112,14 @@ export class ShowCommand extends Command {
this.displayResults(result, options);
}
} catch (error: any) {
displayError(error);
const msg = error?.getSanitizedDetails?.() ?? {
message: error?.message ?? String(error)
};
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
if (error.stack && process.env.DEBUG) {
console.error(chalk.gray(error.stack));
}
process.exit(1);
}
}
@@ -252,15 +257,6 @@ export class ShowCommand extends Command {
return;
}
// Display header with storage info
const activeTag = this.tmCore?.getActiveTag() || 'master';
displayCommandHeader(this.tmCore, {
tag: activeTag,
storageType: result.storageType
});
console.log(); // Add spacing
// Use the global task details display function
displayTaskDetails(result.task, {
statusFilter: options.status,
@@ -275,12 +271,8 @@ export class ShowCommand extends Command {
result: ShowMultipleTasksResult,
_options: ShowCommandOptions
): void {
// Display header with storage info
const activeTag = this.tmCore?.getActiveTag() || 'master';
displayCommandHeader(this.tmCore, {
tag: activeTag,
storageType: result.storageType
});
// Header
ui.displayBanner(`Tasks (${result.tasks.length} found)`);
if (result.notFound.length > 0) {
console.log(chalk.yellow(`\n⚠ Not found: ${result.notFound.join(', ')}`));
@@ -299,6 +291,8 @@ export class ShowCommand extends Command {
showDependencies: true
})
);
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
}
/**

View File

@@ -16,7 +16,6 @@ import {
} from '@tm/core';
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
import * as ui from '../utils/ui.js';
import { displayError } from '../utils/error-handler.js';
/**
* CLI-specific options interface for the start command
@@ -161,7 +160,8 @@ export class StartCommand extends Command {
if (spinner) {
spinner.fail('Operation failed');
}
displayError(error);
this.handleError(error);
process.exit(1);
}
}
@@ -452,6 +452,22 @@ export class StartCommand extends Command {
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
}
/**
* Handle general errors
*/
private handleError(error: any): void {
const msg = error?.getSanitizedDetails?.() ?? {
message: error?.message ?? String(error)
};
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
// Show stack trace in development mode or when DEBUG is set
const isDevelopment = process.env.NODE_ENV !== 'production';
if ((isDevelopment || process.env.DEBUG) && error.stack) {
console.error(chalk.gray(error.stack));
}
}
/**
* Set the last result for programmatic access
*/

View File

@@ -24,9 +24,6 @@ export {
// UI utilities (for other commands to use)
export * as ui from './utils/ui.js';
// Error handling utilities
export { displayError, isDebugMode } from './utils/error-handler.js';
// Auto-update utilities
export {
checkForUpdate,

View File

@@ -5,16 +5,6 @@
import chalk from 'chalk';
/**
* Brief information for API storage
*/
export interface BriefInfo {
briefId: string;
briefName: string;
orgSlug?: string;
webAppUrl?: string;
}
/**
* Header configuration options
*/
@@ -22,44 +12,16 @@ export interface HeaderOptions {
title?: string;
tag?: string;
filePath?: string;
storageType?: 'api' | 'file';
briefInfo?: BriefInfo;
}
/**
* Display the Task Master header with project info
*/
export function displayHeader(options: HeaderOptions = {}): void {
const { filePath, tag, storageType, briefInfo } = options;
const { filePath, tag } = options;
// Display different header based on storage type
if (storageType === 'api' && briefInfo) {
// API storage: Show brief information
const briefDisplay = `🏷 Brief: ${chalk.cyan(briefInfo.briefName)} ${chalk.gray(`(${briefInfo.briefId})`)}`;
console.log(briefDisplay);
// Construct and display the brief URL or ID
if (briefInfo.webAppUrl && briefInfo.orgSlug) {
const briefUrl = `${briefInfo.webAppUrl}/home/${briefInfo.orgSlug}/briefs/${briefInfo.briefId}/plan`;
console.log(`Listing tasks from: ${chalk.dim(briefUrl)}`);
} else if (briefInfo.webAppUrl) {
// Show web app URL and brief ID if org slug is missing
console.log(
`Listing tasks from: ${chalk.dim(`${briefInfo.webAppUrl} (Brief: ${briefInfo.briefId})`)}`
);
console.log(
chalk.yellow(
`💡 Tip: Run ${chalk.cyan('tm context select')} to set your organization and see the full URL`
)
);
} else {
// Fallback: just show the brief ID if we can't get web app URL
console.log(
`Listing tasks from: ${chalk.dim(`API (Brief ID: ${briefInfo.briefId})`)}`
);
}
} else if (tag) {
// File storage: Show tag information
// Display tag and file path info
if (tag) {
let tagInfo = '';
if (tag && tag !== 'master') {

View File

@@ -1,75 +0,0 @@
/**
* @fileoverview Display helper utilities for commands
* Provides DRY utilities for displaying headers and other command output
*/
import type { TaskMasterCore } from '@tm/core';
import type { StorageType } from '@tm/core/types';
import { displayHeader, type BriefInfo } from '../ui/index.js';
/**
* Get web app base URL from environment
*/
function getWebAppUrl(): string | undefined {
const baseDomain =
process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN;
if (!baseDomain) {
return undefined;
}
// If it already includes protocol, use as-is
if (baseDomain.startsWith('http://') || baseDomain.startsWith('https://')) {
return baseDomain;
}
// Otherwise, add protocol based on domain
if (baseDomain.includes('localhost') || baseDomain.includes('127.0.0.1')) {
return `http://${baseDomain}`;
}
return `https://${baseDomain}`;
}
/**
* Display the command header with appropriate storage information
* Handles both API and file storage displays
*/
export function displayCommandHeader(
tmCore: TaskMasterCore | undefined,
options: {
tag?: string;
storageType: Exclude<StorageType, 'auto'>;
}
): void {
const { tag, storageType } = options;
// Get brief info if using API storage
let briefInfo: BriefInfo | undefined;
if (storageType === 'api' && tmCore) {
const storageInfo = tmCore.getStorageDisplayInfo();
if (storageInfo) {
// Construct full brief info with web app URL
briefInfo = {
...storageInfo,
webAppUrl: getWebAppUrl()
};
}
}
// Get file path for display (only for file storage)
// Note: The file structure is fixed for file storage and won't change.
// This is a display-only relative path, not used for actual file operations.
const filePath =
storageType === 'file' && tmCore
? `.taskmaster/tasks/tasks.json`
: undefined;
// Display header
displayHeader({
tag: tag || 'master',
filePath: filePath,
storageType: storageType === 'api' ? 'api' : 'file',
briefInfo: briefInfo
});
}

View File

@@ -1,60 +0,0 @@
/**
* @fileoverview Centralized error handling utilities for CLI
* Provides consistent error formatting and debug mode detection
*/
import chalk from 'chalk';
/**
* Check if debug mode is enabled via environment variable
* Only returns true when DEBUG is explicitly set to 'true' or '1'
*
* @returns True if debug mode is enabled
*/
export function isDebugMode(): boolean {
return process.env.DEBUG === 'true' || process.env.DEBUG === '1';
}
/**
* Display an error to the user with optional stack trace in debug mode
* Handles both TaskMasterError instances and regular errors
*
* @param error - The error to display
* @param options - Display options
*/
export function displayError(
error: any,
options: {
/** Skip exit, useful when caller wants to handle exit */
skipExit?: boolean;
/** Force show stack trace regardless of debug mode */
forceStack?: boolean;
} = {}
): void {
// Check if it's a TaskMasterError with sanitized details
if (error?.getSanitizedDetails) {
const sanitized = error.getSanitizedDetails();
console.error(chalk.red(`\n${sanitized.message}`));
// Show stack trace in debug mode or if forced
if ((isDebugMode() || options.forceStack) && error.stack) {
console.error(chalk.gray('\nStack trace:'));
console.error(chalk.gray(error.stack));
}
} else {
// For other errors, show the message
const message = error?.message ?? String(error);
console.error(chalk.red(`\nError: ${message}`));
// Show stack trace in debug mode or if forced
if ((isDebugMode() || options.forceStack) && error?.stack) {
console.error(chalk.gray('\nStack trace:'));
console.error(chalk.gray(error.stack));
}
}
// Exit if not skipped
if (!options.skipExit) {
process.exit(1);
}
}

View File

@@ -3,24 +3,47 @@ title: CLI Commands
sidebarTitle: "CLI Commands"
---
## Debug Mode
For troubleshooting issues, you can enable debug mode to get detailed error information:
```bash
# Enable debug mode for any command
DEBUG=true task-master <command>
# Example with next command
DEBUG=true task-master next
```
Debug mode shows full error messages, stack traces, and additional context. See the [Troubleshooting guide](/getting-started/troubleshooting) for more details.
## Commands
<AccordionGroup>
<Accordion title="Authentication">
```bash
# Log in to tryhamster.com (opens browser for OAuth authentication)
task-master auth login
# Display current authentication status
task-master auth status
# Log out and clear stored credentials
task-master auth logout
# Refresh authentication token
task-master auth refresh
```
**Note**: After successful login, Task Master will automatically prompt you to configure your workspace context (organization and project selection). If context setup encounters issues, you can configure it later using `task-master context`.
</Accordion>
<Accordion title="Workspace Context">
```bash
# Show current workspace context (organization and brief)
task-master context
# Select an organization
task-master context org
# Select a brief within the current organization
task-master context brief
# Set context directly with IDs
task-master context set --org <orgId> --brief <briefId>
# Clear all context selections
task-master context clear
```
**Note**: Workspace context determines which organization and project brief your tasks and data are associated with. This is automatically configured during initial login but can be changed anytime.
</Accordion>
<Accordion title="Parse PRD">
```bash
# Parse a PRD file and generate tasks

View File

@@ -33,7 +33,6 @@
]
},
"getting-started/api-keys",
"getting-started/troubleshooting",
"getting-started/faq",
"getting-started/contribute"
]

View File

@@ -3,32 +3,7 @@ title: FAQ
sidebarTitle: "FAQ"
---
## Common Questions
### How do I get more detailed error information?
Enable debug mode by setting the `DEBUG` environment variable:
```bash
DEBUG=true task-master <command>
```
This shows full error messages, stack traces, and additional context. See our [Troubleshooting guide](/getting-started/troubleshooting) for complete details.
### Why are my commands failing silently?
Task Master uses sanitized error messages by default to provide a clean user experience. Enable debug mode to see technical details about what went wrong.
### How do I report a bug?
When reporting issues, please:
1. Enable debug mode and include the full error output
2. Provide your environment details (OS, Node.js version, Task Master version)
3. Include the command that failed and your project configuration
See our [Troubleshooting guide](/getting-started/troubleshooting) for complete bug reporting guidelines.
More frequently asked questions coming soon.
Coming soon.
## 💬 Getting Help

View File

@@ -1,155 +0,0 @@
---
title: Troubleshooting
sidebarTitle: "Troubleshooting"
---
This guide helps you troubleshoot common issues and get more detailed error information when using Task Master.
## Debug Mode
Task Master includes a debug mode that provides detailed error information including stack traces. This is especially useful when reporting issues or debugging problems.
### Enabling Debug Mode
Set the `DEBUG` environment variable to enable debug mode:
```bash
# Enable debug mode for a single command
DEBUG=true task-master next
# Or use '1' instead of 'true'
DEBUG=1 task-master list
# Enable for entire session (bash/zsh)
export DEBUG=true
task-master next # Now shows detailed errors
# Enable for entire session (fish shell)
set -x DEBUG true
task-master next
```
### What Debug Mode Shows
With debug mode enabled, errors will include:
- **Full error messages**: Complete technical details instead of sanitized user messages
- **Stack traces**: Detailed information about where errors occurred
- **Error context**: Additional metadata about the failed operation
- **Error chains**: If an error was caused by another error, both are shown
### Example Output
**Normal mode (default):**
```
Error: Unable to read tasks file
```
**Debug mode:**
```
TaskMasterError[FILE_READ_ERROR]: Unable to read tasks file
Stack trace:
at TaskService.loadTasks (/path/to/task-service.ts:123)
at NextCommand.execute (/path/to/next.command.ts:45)
...
```
## Common Issues
### File Permission Errors
**Problem**: Commands fail with file access errors
**Solution**:
1. Check file permissions in `.taskmaster/` directory
2. Ensure your user has write access to the project directory
3. Run with debug mode to see the exact file path causing issues
### API Connection Issues
**Problem**: Commands timeout or fail when using API storage
**Solutions**:
1. Check your internet connection
2. Verify API keys are correctly configured
3. Check if your organization has firewall restrictions
4. Use debug mode to see specific network errors
### Task File Corruption
**Problem**: Commands report invalid task data
**Solutions**:
1. Check if `.taskmaster/tasks/tasks.json` is valid JSON
2. Restore from backup if available
3. Run `task-master generate` to regenerate task files from main data
### Model Configuration Issues
**Problem**: AI-powered commands fail
**Solutions**:
1. Verify API keys are set: `task-master models`
2. Check if the configured model is available
3. Try switching to a different model
4. Use debug mode to see specific API error messages
## Getting Help
When reporting issues, please:
1. **Enable debug mode** and include the full error output
2. **Specify your environment**:
- Operating system and version
- Node.js version (`node --version`)
- Task Master version (`task-master --version`)
3. **Include relevant context**:
- Command that failed
- Project configuration
- Recent changes to your setup
### Support Channels
- **GitHub Issues**: [Report bugs and request features](https://github.com/eyaltoledano/claude-task-master/issues)
- **Discord**: [Join our community for help](https://discord.gg/taskmasterai)
- **GitHub Discussions**: [Ask questions and share ideas](https://github.com/eyaltoledano/claude-task-master/discussions)
## Advanced Debugging
### Inspecting Task Data
If you suspect task data corruption:
```bash
# View raw task data
cat .taskmaster/tasks/tasks.json | jq '.'
# Check specific task
task-master show <task-id> --format=json
```
### Configuration Debugging
Check your current configuration:
```bash
# View model configuration
task-master models
# Check if initialization is complete
ls -la .taskmaster/
```
### Network Debugging
For API storage issues:
```bash
# Test with curl (replace with your API endpoint)
curl -H "Authorization: Bearer $ANTHROPIC_API_KEY" \
-H "Content-Type: application/json" \
https://api.anthropic.com/v1/models
```
Remember to enable debug mode (`DEBUG=true`) when troubleshooting to get the most helpful error information.

File diff suppressed because one or more lines are too long

View File

@@ -16,7 +16,6 @@ export interface AuthCredentials {
export interface UserContext {
orgId?: string;
orgName?: string;
orgSlug?: string;
briefId?: string;
briefName?: string;
updatedAt: string;

View File

@@ -52,10 +52,7 @@ export const ERROR_CODES = {
INVALID_INPUT: 'INVALID_INPUT',
NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
NOT_FOUND: 'NOT_FOUND',
// Context errors
NO_BRIEF_SELECTED: 'NO_BRIEF_SELECTED'
NOT_FOUND: 'NOT_FOUND'
} as const;
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];

View File

@@ -161,16 +161,6 @@ export class TaskService {
storageType: this.getStorageType()
};
} catch (error) {
// If it's a user-facing error (like NO_BRIEF_SELECTED), don't log it as an internal error
if (
error instanceof TaskMasterError &&
error.is(ERROR_CODES.NO_BRIEF_SELECTED)
) {
// Just re-throw user-facing errors without wrapping
throw error;
}
// Log internal errors
this.logger.error('Failed to get task list', error);
throw new TaskMasterError(
'Failed to get task list',
@@ -196,14 +186,6 @@ export class TaskService {
// Delegate to storage layer which handles the specific logic for tasks vs subtasks
return await this.storage.loadTask(String(taskId), activeTag);
} catch (error) {
// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
if (
error instanceof TaskMasterError &&
error.is(ERROR_CODES.NO_BRIEF_SELECTED)
) {
throw error;
}
throw new TaskMasterError(
`Failed to get task ${taskId}`,
ERROR_CODES.STORAGE_ERROR,
@@ -540,14 +522,6 @@ export class TaskService {
activeTag
);
} catch (error) {
// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
if (
error instanceof TaskMasterError &&
error.is(ERROR_CODES.NO_BRIEF_SELECTED)
) {
throw error;
}
throw new TaskMasterError(
`Failed to update task status for ${taskIdStr}`,
ERROR_CODES.STORAGE_ERROR,

View File

@@ -37,13 +37,6 @@ export interface ApiStorageConfig {
maxRetries?: number;
}
/**
* Auth context with a guaranteed briefId
*/
type ContextWithBrief = NonNullable<
ReturnType<typeof AuthManager.prototype.getContext>
> & { briefId: string };
/**
* ApiStorage implementation using repository pattern
* Provides flexibility to swap between different backend implementations
@@ -133,7 +126,7 @@ export class ApiStorage implements IStorage {
private async loadTagsIntoCache(): Promise<void> {
try {
const authManager = AuthManager.getInstance();
const context = authManager.getContext();
const context = await authManager.getContext();
// If we have a selected brief, create a virtual "tag" for it
if (context?.briefId) {
@@ -165,7 +158,15 @@ export class ApiStorage implements IStorage {
await this.ensureInitialized();
try {
const context = this.ensureBriefSelected('loadTasks');
const authManager = AuthManager.getInstance();
const context = await authManager.getContext();
// If no brief is selected in context, throw an error
if (!context?.briefId) {
throw new Error(
'No brief selected. Please select a brief first using: tm context brief <brief-id>'
);
}
// Load tasks from the current brief context with filters pushed to repository
const tasks = await this.retryOperation(() =>
@@ -180,11 +181,12 @@ export class ApiStorage implements IStorage {
return tasks;
} catch (error) {
this.wrapError(error, 'Failed to load tasks from API', {
operation: 'loadTasks',
tag,
context: 'brief-based loading'
});
throw new TaskMasterError(
'Failed to load tasks from API',
ERROR_CODES.STORAGE_ERROR,
{ operation: 'loadTasks', tag, context: 'brief-based loading' },
error as Error
);
}
}
@@ -235,17 +237,16 @@ export class ApiStorage implements IStorage {
await this.ensureInitialized();
try {
this.ensureBriefSelected('loadTask');
return await this.retryOperation(() =>
this.repository.getTask(this.projectId, taskId)
);
} catch (error) {
this.wrapError(error, 'Failed to load task from API', {
operation: 'loadTask',
taskId,
tag
});
throw new TaskMasterError(
'Failed to load task from API',
ERROR_CODES.STORAGE_ERROR,
{ operation: 'loadTask', taskId, tag },
error as Error
);
}
}
@@ -324,7 +325,7 @@ export class ApiStorage implements IStorage {
try {
const authManager = AuthManager.getInstance();
const context = authManager.getContext();
const context = await authManager.getContext();
// In our API-based system, we only have one "tag" at a time - the current brief
if (context?.briefId) {
@@ -509,8 +510,6 @@ export class ApiStorage implements IStorage {
await this.ensureInitialized();
try {
this.ensureBriefSelected('updateTaskStatus');
const existingTask = await this.retryOperation(() =>
this.repository.getTask(this.projectId, taskId)
);
@@ -547,12 +546,12 @@ export class ApiStorage implements IStorage {
taskId
};
} catch (error) {
this.wrapError(error, 'Failed to update task status via API', {
operation: 'updateTaskStatus',
taskId,
newStatus,
tag
});
throw new TaskMasterError(
'Failed to update task status via API',
ERROR_CODES.STORAGE_ERROR,
{ operation: 'updateTaskStatus', taskId, newStatus, tag },
error as Error
);
}
}
@@ -770,29 +769,6 @@ export class ApiStorage implements IStorage {
}
}
/**
* Ensure a brief is selected in the current context
* @returns The current auth context with a valid briefId
*/
private ensureBriefSelected(operation: string): ContextWithBrief {
const authManager = AuthManager.getInstance();
const context = authManager.getContext();
if (!context?.briefId) {
throw new TaskMasterError(
'No brief selected',
ERROR_CODES.NO_BRIEF_SELECTED,
{
operation,
userMessage:
'No brief selected. Please select a brief first using: tm context brief <brief-id> or tm context brief <brief-url>'
}
);
}
return context as ContextWithBrief;
}
/**
* Retry an operation with exponential backoff
*/
@@ -811,28 +787,4 @@ export class ApiStorage implements IStorage {
throw error;
}
}
/**
* Wrap an error unless it's already a NO_BRIEF_SELECTED error
*/
private wrapError(
error: unknown,
message: string,
context: Record<string, unknown>
): never {
// If it's already a NO_BRIEF_SELECTED error, don't wrap it
if (
error instanceof TaskMasterError &&
error.is(ERROR_CODES.NO_BRIEF_SELECTED)
) {
throw error;
}
throw new TaskMasterError(
message,
ERROR_CODES.STORAGE_ERROR,
context,
error as Error
);
}
}

View File

@@ -201,44 +201,6 @@ export class TaskMasterCore {
return this.taskService.getStorageType();
}
/**
* Get storage configuration
*/
getStorageConfig() {
return this.configManager.getStorageConfig();
}
/**
* Get storage display information for headers
* Returns context info for API storage, null for file storage
*/
getStorageDisplayInfo(): {
briefId: string;
briefName: string;
orgSlug?: string;
} | null {
// Only return info if using API storage
const storageType = this.getStorageType();
if (storageType !== 'api') {
return null;
}
// Get credentials from auth manager
const authManager = AuthManager.getInstance();
const credentials = authManager.getCredentials();
const selectedContext = credentials?.selectedContext;
if (!selectedContext?.briefId || !selectedContext?.briefName) {
return null;
}
return {
briefId: selectedContext.briefId,
briefName: selectedContext.briefName,
orgSlug: selectedContext.orgSlug
};
}
/**
* Get current active tag
*/

View File

@@ -19,8 +19,7 @@ import {
registerAllCommands,
checkForUpdate,
performAutoUpdate,
displayUpgradeNotification,
displayError
displayUpgradeNotification
} from '@tm/cli';
import {
@@ -5157,7 +5156,10 @@ async function runCLI(argv = process.argv) {
);
} else {
// Generic error handling for other errors
displayError(error);
console.error(chalk.red(`Error: ${error.message}`));
if (getDebugFlag()) {
console.error(error);
}
}
process.exit(1);