mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-30 06:12:05 +00:00
fix: suppress config warnings during Sentry init and API mode detection (#1482)
This commit is contained in:
@@ -9,7 +9,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { findProjectRoot } from '@tm/core';
|
import { AuthManager, findProjectRoot } from '@tm/core';
|
||||||
|
import { setSuppressConfigWarnings } from './modules/config-manager.js';
|
||||||
|
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { initializeSentry } from '../src/telemetry/sentry.js';
|
import { initializeSentry } from '../src/telemetry/sentry.js';
|
||||||
|
|
||||||
@@ -35,6 +37,19 @@ if (process.env.DEBUG === '1') {
|
|||||||
console.error('DEBUG - dev.js received args:', process.argv.slice(2));
|
console.error('DEBUG - dev.js received args:', process.argv.slice(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Suppress config warnings if user is authenticated (API mode)
|
||||||
|
// When authenticated, we don't need local config - everything is remote
|
||||||
|
try {
|
||||||
|
const authManager = AuthManager.getInstance();
|
||||||
|
const hasValidSession = await authManager.hasValidSession();
|
||||||
|
if (hasValidSession) {
|
||||||
|
setSuppressConfigWarnings(true);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setSuppressConfigWarnings(false);
|
||||||
|
// Auth check failed, continue without suppressing
|
||||||
|
}
|
||||||
|
|
||||||
// Use dynamic import to ensure dotenv.config() runs before module-level code executes
|
// Use dynamic import to ensure dotenv.config() runs before module-level code executes
|
||||||
const { runCLI } = await import('./modules/commands.js');
|
const { runCLI } = await import('./modules/commands.js');
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ import {
|
|||||||
getDebugFlag,
|
getDebugFlag,
|
||||||
getDefaultNumTasks,
|
getDefaultNumTasks,
|
||||||
isApiKeySet,
|
isApiKeySet,
|
||||||
isConfigFilePresent
|
isConfigFilePresent,
|
||||||
|
setSuppressConfigWarnings
|
||||||
} from './config-manager.js';
|
} from './config-manager.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -161,13 +162,17 @@ function isConnectedToHamster() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: Check if storage type is 'api' (user selected Hamster during init)
|
// Fallback: Check if storage type is 'api' (user selected Hamster during init)
|
||||||
|
// Suppress warnings during this check since we're detecting API mode
|
||||||
|
setSuppressConfigWarnings(true);
|
||||||
try {
|
try {
|
||||||
const config = getConfig();
|
const config = getConfig(null, false, { storageType: 'api' });
|
||||||
if (config?.storage?.type === 'api') {
|
if (config?.storage?.type === 'api') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Config check failed, continue
|
// Config check failed, continue
|
||||||
|
} finally {
|
||||||
|
setSuppressConfigWarnings(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -72,6 +72,23 @@ const DEFAULTS = {
|
|||||||
let loadedConfig = null;
|
let loadedConfig = null;
|
||||||
let loadedConfigRoot = null; // Track which root loaded the config
|
let loadedConfigRoot = null; // Track which root loaded the config
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suppress config file warnings (useful during API mode detection)
|
||||||
|
* Uses global object so it can be shared across modules without circular deps
|
||||||
|
* @param {boolean} suppress - Whether to suppress warnings
|
||||||
|
*/
|
||||||
|
export function setSuppressConfigWarnings(suppress) {
|
||||||
|
global._tmSuppressConfigWarnings = suppress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if config warnings are currently suppressed
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isConfigWarningSuppressed() {
|
||||||
|
return global._tmSuppressConfigWarnings === true;
|
||||||
|
}
|
||||||
|
|
||||||
// Custom Error for configuration issues
|
// Custom Error for configuration issues
|
||||||
class ConfigurationError extends Error {
|
class ConfigurationError extends Error {
|
||||||
constructor(message) {
|
constructor(message) {
|
||||||
@@ -80,9 +97,10 @@ class ConfigurationError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _loadAndValidateConfig(explicitRoot = null) {
|
function _loadAndValidateConfig(explicitRoot = null, options = {}) {
|
||||||
const defaults = DEFAULTS; // Use the defined defaults
|
const defaults = DEFAULTS; // Use the defined defaults
|
||||||
let rootToUse = explicitRoot;
|
let rootToUse = explicitRoot;
|
||||||
|
const { storageType } = options;
|
||||||
let configSource = explicitRoot
|
let configSource = explicitRoot
|
||||||
? `explicit root (${explicitRoot})`
|
? `explicit root (${explicitRoot})`
|
||||||
: 'defaults (no root provided yet)';
|
: 'defaults (no root provided yet)';
|
||||||
@@ -114,7 +132,7 @@ function _loadAndValidateConfig(explicitRoot = null) {
|
|||||||
if (hasProjectMarkers) {
|
if (hasProjectMarkers) {
|
||||||
// Only try to find config if we have project markers
|
// Only try to find config if we have project markers
|
||||||
// This prevents the repeated warnings during init
|
// This prevents the repeated warnings during init
|
||||||
configPath = findConfigPath(null, { projectRoot: rootToUse });
|
configPath = findConfigPath(null, { projectRoot: rootToUse, storageType });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configPath) {
|
if (configPath) {
|
||||||
@@ -203,29 +221,36 @@ function _loadAndValidateConfig(explicitRoot = null) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Config file doesn't exist at the determined rootToUse.
|
// Config file doesn't exist at the determined rootToUse.
|
||||||
if (explicitRoot) {
|
// Skip warnings if:
|
||||||
// Only warn if an explicit root was *expected*.
|
// 1. Global suppress flag is set (during API mode detection)
|
||||||
console.warn(
|
// 2. storageType is explicitly 'api' (remote storage mode - no local config expected)
|
||||||
chalk.yellow(
|
const shouldWarn = !isConfigWarningSuppressed() && storageType !== 'api';
|
||||||
`Warning: Configuration file not found at provided project root (${explicitRoot}). Using default configuration. Run 'task-master models --setup' to configure.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Don't warn about missing config during initialization
|
|
||||||
// Only warn if this looks like an existing project (has .taskmaster dir or legacy config marker)
|
|
||||||
const hasTaskmasterDir = fs.existsSync(
|
|
||||||
path.join(rootToUse, TASKMASTER_DIR)
|
|
||||||
);
|
|
||||||
const hasLegacyMarker = fs.existsSync(
|
|
||||||
path.join(rootToUse, LEGACY_CONFIG_FILE)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasTaskmasterDir || hasLegacyMarker) {
|
if (shouldWarn) {
|
||||||
|
if (explicitRoot) {
|
||||||
|
// Warn about explicit root not having config
|
||||||
console.warn(
|
console.warn(
|
||||||
chalk.yellow(
|
chalk.yellow(
|
||||||
`Warning: Configuration file not found at derived root (${rootToUse}). Using defaults.`
|
`Warning: Configuration file not found at provided project root (${explicitRoot}). Using default configuration. Run 'task-master models --setup' to configure.`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Don't warn about missing config during initialization
|
||||||
|
// Only warn if this looks like an existing project (has .taskmaster dir or legacy config marker)
|
||||||
|
const hasTaskmasterDir = fs.existsSync(
|
||||||
|
path.join(rootToUse, TASKMASTER_DIR)
|
||||||
|
);
|
||||||
|
const hasLegacyMarker = fs.existsSync(
|
||||||
|
path.join(rootToUse, LEGACY_CONFIG_FILE)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasTaskmasterDir || hasLegacyMarker) {
|
||||||
|
console.warn(
|
||||||
|
chalk.yellow(
|
||||||
|
`Warning: Configuration file not found at derived root (${rootToUse}). Using defaults.`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Keep config as defaults
|
// Keep config as defaults
|
||||||
@@ -241,9 +266,11 @@ function _loadAndValidateConfig(explicitRoot = null) {
|
|||||||
* Handles MCP initialization context gracefully.
|
* Handles MCP initialization context gracefully.
|
||||||
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
||||||
* @param {boolean} forceReload - Force reloading the config file.
|
* @param {boolean} forceReload - Force reloading the config file.
|
||||||
|
* @param {object} options - Optional configuration options.
|
||||||
|
* @param {'api'|'file'|'auto'} [options.storageType] - Storage type to suppress warnings for API mode.
|
||||||
* @returns {object} The loaded configuration object.
|
* @returns {object} The loaded configuration object.
|
||||||
*/
|
*/
|
||||||
function getConfig(explicitRoot = null, forceReload = false) {
|
function getConfig(explicitRoot = null, forceReload = false, options = {}) {
|
||||||
// Determine if a reload is necessary
|
// Determine if a reload is necessary
|
||||||
const needsLoad =
|
const needsLoad =
|
||||||
!loadedConfig ||
|
!loadedConfig ||
|
||||||
@@ -251,7 +278,7 @@ function getConfig(explicitRoot = null, forceReload = false) {
|
|||||||
(explicitRoot && explicitRoot !== loadedConfigRoot);
|
(explicitRoot && explicitRoot !== loadedConfigRoot);
|
||||||
|
|
||||||
if (needsLoad) {
|
if (needsLoad) {
|
||||||
const newConfig = _loadAndValidateConfig(explicitRoot); // _load handles null explicitRoot
|
const newConfig = _loadAndValidateConfig(explicitRoot, options); // _load handles null explicitRoot
|
||||||
|
|
||||||
// Only update the global cache if loading was forced or if an explicit root
|
// Only update the global cache if loading was forced or if an explicit root
|
||||||
// was provided (meaning we attempted to load a specific project's config).
|
// was provided (meaning we attempted to load a specific project's config).
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { createHash } from 'crypto';
|
|||||||
* Provides error tracking and AI operation monitoring
|
* Provides error tracking and AI operation monitoring
|
||||||
*/
|
*/
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { getAnonymousTelemetryEnabled } from '../../scripts/modules/config-manager.js';
|
import {
|
||||||
import { resolveEnvVariable } from '../../scripts/modules/utils.js';
|
getAnonymousTelemetryEnabled,
|
||||||
|
setSuppressConfigWarnings
|
||||||
|
} from '../../scripts/modules/config-manager.js';
|
||||||
|
|
||||||
let isInitialized = false;
|
let isInitialized = false;
|
||||||
|
|
||||||
@@ -41,6 +43,8 @@ export function initializeSentry(options = {}) {
|
|||||||
// Check if user has opted out of anonymous telemetry
|
// Check if user has opted out of anonymous telemetry
|
||||||
// This applies to local storage users only
|
// This applies to local storage users only
|
||||||
// Hamster users don't use local config (API storage), so this check doesn't affect them
|
// Hamster users don't use local config (API storage), so this check doesn't affect them
|
||||||
|
// Suppress config warnings during this check to avoid noisy output at startup
|
||||||
|
setSuppressConfigWarnings(true);
|
||||||
try {
|
try {
|
||||||
const telemetryEnabled = getAnonymousTelemetryEnabled(options.projectRoot);
|
const telemetryEnabled = getAnonymousTelemetryEnabled(options.projectRoot);
|
||||||
|
|
||||||
@@ -54,6 +58,8 @@ export function initializeSentry(options = {}) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If there's an error checking telemetry preferences (e.g., config not available yet),
|
// If there's an error checking telemetry preferences (e.g., config not available yet),
|
||||||
// default to enabled. This ensures telemetry works during initialization.
|
// default to enabled. This ensures telemetry works during initialization.
|
||||||
|
} finally {
|
||||||
|
setSuppressConfigWarnings(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use internal Sentry DSN for Task Master telemetry
|
// Use internal Sentry DSN for Task Master telemetry
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
TASKMASTER_TASKS_FILE
|
TASKMASTER_TASKS_FILE
|
||||||
} from '../constants/paths.js';
|
} from '../constants/paths.js';
|
||||||
import { getLoggerOrDefault } from './logger-utils.js';
|
import { getLoggerOrDefault } from './logger-utils.js';
|
||||||
|
import { isConfigWarningSuppressed } from '../../scripts/modules/config-manager.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize project root to ensure it doesn't end with .taskmaster
|
* Normalize project root to ensure it doesn't end with .taskmaster
|
||||||
@@ -453,15 +454,22 @@ export function findConfigPath(explicitPath = null, args = null, log = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only warn once per command execution to prevent spam during init
|
// Only warn once per command execution to prevent spam during init
|
||||||
const warningKey = `config_warning_${projectRoot}`;
|
// Skip warning if:
|
||||||
|
// Global suppress flag is set (during API mode detection)
|
||||||
|
const shouldSkipWarning =
|
||||||
|
isConfigWarningSuppressed() || args?.storageType === 'api';
|
||||||
|
|
||||||
if (!global._tmConfigWarningsThisRun) {
|
if (!shouldSkipWarning) {
|
||||||
global._tmConfigWarningsThisRun = new Set();
|
const warningKey = `config_warning_${projectRoot}`;
|
||||||
}
|
|
||||||
|
|
||||||
if (!global._tmConfigWarningsThisRun.has(warningKey)) {
|
if (!global._tmConfigWarningsThisRun) {
|
||||||
global._tmConfigWarningsThisRun.add(warningKey);
|
global._tmConfigWarningsThisRun = new Set();
|
||||||
logger.warn?.(`No configuration file found in project: ${projectRoot}`);
|
}
|
||||||
|
|
||||||
|
if (!global._tmConfigWarningsThisRun.has(warningKey)) {
|
||||||
|
global._tmConfigWarningsThisRun.add(warningKey);
|
||||||
|
logger.warn?.(`No configuration file found in project: ${projectRoot}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
|
|||||||
getDebugFlag: jest.fn(() => false),
|
getDebugFlag: jest.fn(() => false),
|
||||||
getLogLevel: jest.fn(() => 'info'),
|
getLogLevel: jest.fn(() => 'info'),
|
||||||
isProxyEnabled: jest.fn(() => false),
|
isProxyEnabled: jest.fn(() => false),
|
||||||
getAnonymousTelemetryEnabled: jest.fn(() => true)
|
getAnonymousTelemetryEnabled: jest.fn(() => true),
|
||||||
|
setSuppressConfigWarnings: jest.fn(),
|
||||||
|
isConfigWarningSuppressed: jest.fn(() => false)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock utils
|
// Mock utils
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
|
|||||||
|
|
||||||
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
|
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
|
||||||
isProxyEnabled: jest.fn(() => false),
|
isProxyEnabled: jest.fn(() => false),
|
||||||
getAnonymousTelemetryEnabled: jest.fn(() => true)
|
getAnonymousTelemetryEnabled: jest.fn(() => true),
|
||||||
|
setSuppressConfigWarnings: jest.fn(),
|
||||||
|
isConfigWarningSuppressed: jest.fn(() => false)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Import after mocking
|
// Import after mocking
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
|
|||||||
|
|
||||||
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
|
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
|
||||||
isProxyEnabled: jest.fn(() => false),
|
isProxyEnabled: jest.fn(() => false),
|
||||||
getAnonymousTelemetryEnabled: jest.fn(() => true)
|
getAnonymousTelemetryEnabled: jest.fn(() => true),
|
||||||
|
setSuppressConfigWarnings: jest.fn(),
|
||||||
|
isConfigWarningSuppressed: jest.fn(() => false)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Import after mocking
|
// Import after mocking
|
||||||
|
|||||||
@@ -189,7 +189,9 @@ jest.unstable_mockModule(
|
|||||||
getAllProviders: jest.fn(() => ['anthropic', 'openai', 'perplexity']),
|
getAllProviders: jest.fn(() => ['anthropic', 'openai', 'perplexity']),
|
||||||
getVertexProjectId: jest.fn(() => undefined),
|
getVertexProjectId: jest.fn(() => undefined),
|
||||||
getVertexLocation: jest.fn(() => undefined),
|
getVertexLocation: jest.fn(() => undefined),
|
||||||
hasCodebaseAnalysis: jest.fn(() => false)
|
hasCodebaseAnalysis: jest.fn(() => false),
|
||||||
|
setSuppressConfigWarnings: jest.fn(),
|
||||||
|
isConfigWarningSuppressed: jest.fn(() => false)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user