fix: suppress config warnings during Sentry init and API mode detection (#1482)

This commit is contained in:
Ralph Khreish
2025-12-03 23:08:49 +01:00
committed by GitHub
parent 8674007cce
commit 0e7c068e86
9 changed files with 107 additions and 38 deletions

View File

@@ -9,7 +9,9 @@
*/
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 { 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));
}
// 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
const { runCLI } = await import('./modules/commands.js');

View File

@@ -72,7 +72,8 @@ import {
getDebugFlag,
getDefaultNumTasks,
isApiKeySet,
isConfigFilePresent
isConfigFilePresent,
setSuppressConfigWarnings
} from './config-manager.js';
import {
@@ -161,13 +162,17 @@ function isConnectedToHamster() {
}
// 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 {
const config = getConfig();
const config = getConfig(null, false, { storageType: 'api' });
if (config?.storage?.type === 'api') {
return true;
}
} catch {
// Config check failed, continue
} finally {
setSuppressConfigWarnings(false);
}
return false;

View File

@@ -72,6 +72,23 @@ const DEFAULTS = {
let loadedConfig = null;
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
class ConfigurationError extends Error {
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
let rootToUse = explicitRoot;
const { storageType } = options;
let configSource = explicitRoot
? `explicit root (${explicitRoot})`
: 'defaults (no root provided yet)';
@@ -114,7 +132,7 @@ function _loadAndValidateConfig(explicitRoot = null) {
if (hasProjectMarkers) {
// Only try to find config if we have project markers
// This prevents the repeated warnings during init
configPath = findConfigPath(null, { projectRoot: rootToUse });
configPath = findConfigPath(null, { projectRoot: rootToUse, storageType });
}
if (configPath) {
@@ -203,29 +221,36 @@ function _loadAndValidateConfig(explicitRoot = null) {
}
} else {
// Config file doesn't exist at the determined rootToUse.
if (explicitRoot) {
// Only warn if an explicit root was *expected*.
console.warn(
chalk.yellow(
`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)
);
// Skip warnings if:
// 1. Global suppress flag is set (during API mode detection)
// 2. storageType is explicitly 'api' (remote storage mode - no local config expected)
const shouldWarn = !isConfigWarningSuppressed() && storageType !== 'api';
if (hasTaskmasterDir || hasLegacyMarker) {
if (shouldWarn) {
if (explicitRoot) {
// Warn about explicit root not having config
console.warn(
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
@@ -241,9 +266,11 @@ function _loadAndValidateConfig(explicitRoot = null) {
* Handles MCP initialization context gracefully.
* @param {string|null} explicitRoot - Optional explicit path to the project root.
* @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.
*/
function getConfig(explicitRoot = null, forceReload = false) {
function getConfig(explicitRoot = null, forceReload = false, options = {}) {
// Determine if a reload is necessary
const needsLoad =
!loadedConfig ||
@@ -251,7 +278,7 @@ function getConfig(explicitRoot = null, forceReload = false) {
(explicitRoot && explicitRoot !== loadedConfigRoot);
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
// was provided (meaning we attempted to load a specific project's config).

View File

@@ -4,8 +4,10 @@ import { createHash } from 'crypto';
* Provides error tracking and AI operation monitoring
*/
import * as Sentry from '@sentry/node';
import { getAnonymousTelemetryEnabled } from '../../scripts/modules/config-manager.js';
import { resolveEnvVariable } from '../../scripts/modules/utils.js';
import {
getAnonymousTelemetryEnabled,
setSuppressConfigWarnings
} from '../../scripts/modules/config-manager.js';
let isInitialized = false;
@@ -41,6 +43,8 @@ export function initializeSentry(options = {}) {
// Check if user has opted out of anonymous telemetry
// This applies to local storage users only
// 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 {
const telemetryEnabled = getAnonymousTelemetryEnabled(options.projectRoot);
@@ -54,6 +58,8 @@ export function initializeSentry(options = {}) {
} catch (error) {
// If there's an error checking telemetry preferences (e.g., config not available yet),
// default to enabled. This ensures telemetry works during initialization.
} finally {
setSuppressConfigWarnings(false);
}
// Use internal Sentry DSN for Task Master telemetry

View File

@@ -23,6 +23,7 @@ import {
TASKMASTER_TASKS_FILE
} from '../constants/paths.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
@@ -453,15 +454,22 @@ export function findConfigPath(explicitPath = null, args = null, log = null) {
}
// 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) {
global._tmConfigWarningsThisRun = new Set();
}
if (!shouldSkipWarning) {
const warningKey = `config_warning_${projectRoot}`;
if (!global._tmConfigWarningsThisRun.has(warningKey)) {
global._tmConfigWarningsThisRun.add(warningKey);
logger.warn?.(`No configuration file found in project: ${projectRoot}`);
if (!global._tmConfigWarningsThisRun) {
global._tmConfigWarningsThisRun = new Set();
}
if (!global._tmConfigWarningsThisRun.has(warningKey)) {
global._tmConfigWarningsThisRun.add(warningKey);
logger.warn?.(`No configuration file found in project: ${projectRoot}`);
}
}
return null;

View File

@@ -63,7 +63,9 @@ jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
getDebugFlag: jest.fn(() => false),
getLogLevel: jest.fn(() => 'info'),
isProxyEnabled: jest.fn(() => false),
getAnonymousTelemetryEnabled: jest.fn(() => true)
getAnonymousTelemetryEnabled: jest.fn(() => true),
setSuppressConfigWarnings: jest.fn(),
isConfigWarningSuppressed: jest.fn(() => false)
}));
// Mock utils

View File

@@ -38,7 +38,9 @@ jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
isProxyEnabled: jest.fn(() => false),
getAnonymousTelemetryEnabled: jest.fn(() => true)
getAnonymousTelemetryEnabled: jest.fn(() => true),
setSuppressConfigWarnings: jest.fn(),
isConfigWarningSuppressed: jest.fn(() => false)
}));
// Import after mocking

View File

@@ -17,7 +17,9 @@ jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
isProxyEnabled: jest.fn(() => false),
getAnonymousTelemetryEnabled: jest.fn(() => true)
getAnonymousTelemetryEnabled: jest.fn(() => true),
setSuppressConfigWarnings: jest.fn(),
isConfigWarningSuppressed: jest.fn(() => false)
}));
// Import after mocking

View File

@@ -189,7 +189,9 @@ jest.unstable_mockModule(
getAllProviders: jest.fn(() => ['anthropic', 'openai', 'perplexity']),
getVertexProjectId: jest.fn(() => undefined),
getVertexLocation: jest.fn(() => undefined),
hasCodebaseAnalysis: jest.fn(() => false)
hasCodebaseAnalysis: jest.fn(() => false),
setSuppressConfigWarnings: jest.fn(),
isConfigWarningSuppressed: jest.fn(() => false)
})
);