From f65a751ff0bfa8f9841c6d17f94ecc1009f2ae0b Mon Sep 17 00:00:00 2001 From: jnjpng Date: Mon, 16 Mar 2026 13:14:25 -0700 Subject: [PATCH] fix(debug): unify LETTA_DEBUG and DEBUG gating (#1408) Co-authored-by: Letta Code --- src/agent/check-approval.ts | 4 ++-- src/agent/message.ts | 4 ++-- src/cli/App.tsx | 11 ++++++++--- src/headless.ts | 4 ++-- src/index.ts | 10 +++++----- src/queue/queueRuntime.ts | 3 ++- src/utils/debug.ts | 17 ++++++++++++----- src/websocket/listen-client.ts | 27 ++++++++++++++------------- 8 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/agent/check-approval.ts b/src/agent/check-approval.ts index 8b070ea..ef50d9c 100644 --- a/src/agent/check-approval.ts +++ b/src/agent/check-approval.ts @@ -6,7 +6,7 @@ import { APIError } from "@letta-ai/letta-client/core/error"; import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"; import type { Message } from "@letta-ai/letta-client/resources/agents/messages"; import type { ApprovalRequest } from "../cli/helpers/stream"; -import { debugWarn } from "../utils/debug"; +import { debugWarn, isDebugEnabled } from "../utils/debug"; // Backfill should feel like "the last turn(s)", not "the last N raw messages". // Tool-heavy turns can generate many tool_call/tool_return messages that would @@ -484,7 +484,7 @@ export async function getResumeData( }); messages = sortChronological(messagesPage.getPaginatedItems()); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log( `[DEBUG] conversations.messages.list(default, agent_id=${agent.id}) returned ${messages.length} messages`, ); diff --git a/src/agent/message.ts b/src/agent/message.ts index a0e7150..7359652 100644 --- a/src/agent/message.ts +++ b/src/agent/message.ts @@ -14,7 +14,7 @@ import { captureToolExecutionContext, waitForToolsetReady, } from "../tools/manager"; -import { debugLog, debugWarn } from "../utils/debug"; +import { debugLog, debugWarn, isDebugEnabled } from "../utils/debug"; import { isTimingsEnabled } from "../utils/timing"; import { type ApprovalNormalizationOptions, @@ -138,7 +138,7 @@ export async function sendMessageStream( clientSkills, ); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log( `[DEBUG] sendMessageStream: conversationId=${conversationId}, agentId=${opts.agentId ?? "(none)"}`, ); diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 3e2b594..90bd45b 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -121,7 +121,12 @@ import { } from "../tools/manager"; import type { ToolsetName, ToolsetPreference } from "../tools/toolset"; import { formatToolsetName } from "../tools/toolset-labels"; -import { debugLog, debugLogFile, debugWarn } from "../utils/debug"; +import { + debugLog, + debugLogFile, + debugWarn, + isDebugEnabled, +} from "../utils/debug"; import { getVersion } from "../version"; import { handleMcpAdd, @@ -3050,7 +3055,7 @@ export default function App({ const agentName = agentState?.name || "Unnamed Agent"; const isResumingConversation = resumedExistingConversation || messageHistory.length > 0; - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log( `[DEBUG] Header: resumedExistingConversation=${resumedExistingConversation}, messageHistory.length=${messageHistory.length}`, ); @@ -4721,7 +4726,7 @@ export default function App({ }) .catch((err) => { // Silently ignore - not critical - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error( "[DEBUG] Failed to set conversation summary:", err, diff --git a/src/headless.ts b/src/headless.ts index d1a7bf2..547af82 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -120,7 +120,7 @@ import type { StreamEvent, SystemInitMessage, } from "./types/protocol"; -import { debugLog, debugWarn } from "./utils/debug"; +import { debugLog, debugWarn, isDebugEnabled } from "./utils/debug"; import { markMilestone, measureSinceMilestone, @@ -1493,7 +1493,7 @@ ${SYSTEM_REMINDER_CLOSE} agentId: agent.id, skillSources: resolvedSkillSources, logger: (message) => { - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.warn(`[DEBUG] ${message}`); } }, diff --git a/src/index.ts b/src/index.ts index 10283a9..7a577c2 100755 --- a/src/index.ts +++ b/src/index.ts @@ -49,7 +49,7 @@ import { startStartupAutoUpdateCheck } from "./startup-auto-update"; import { telemetry } from "./telemetry"; import { loadTools } from "./tools/manager"; import { clearPersistedClientToolRules } from "./tools/toolset"; -import { debugLog, debugWarn } from "./utils/debug"; +import { debugLog, debugWarn, isDebugEnabled } from "./utils/debug"; import { markMilestone } from "./utils/timing"; // Stable empty array constants to prevent new references on every render @@ -777,7 +777,7 @@ async function main(): Promise { const message = err instanceof Error ? err.message : "An unexpected error occurred"; console.error(`\nError: ${message}`); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error(err); } process.exit(1); @@ -1824,7 +1824,7 @@ async function main(): Promise { let conversationIdToUse!: string; // Debug: log resume flag status - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log(`[DEBUG] shouldContinue=${shouldContinue}`); console.log(`[DEBUG] shouldResume=${shouldResume}`); console.log( @@ -1865,7 +1865,7 @@ async function main(): Promise { settingsManager.getLocalLastSession(process.cwd()) ?? settingsManager.getGlobalLastSession(); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log(`[DEBUG] lastSession=${JSON.stringify(lastSession)}`); console.log(`[DEBUG] agent.id=${agent.id}`); } @@ -2029,7 +2029,7 @@ async function main(): Promise { // Handle errors gracefully without showing raw stack traces const message = formatErrorDetails(err); console.error(`\nError during initialization: ${message}`); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error(err); } process.exit(1); diff --git a/src/queue/queueRuntime.ts b/src/queue/queueRuntime.ts index 8a5639e..fd73ed1 100644 --- a/src/queue/queueRuntime.ts +++ b/src/queue/queueRuntime.ts @@ -6,6 +6,7 @@ import type { QueueItemKind, QueueItemSource, } from "../types/protocol"; +import { isDebugEnabled } from "../utils/debug"; export type { QueueBlockedReason, QueueClearedReason, QueueItemKind }; @@ -362,7 +363,7 @@ export class QueueRuntime { ...args, ); } catch (err) { - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error(`[QueueRuntime] callback "${name}" threw:`, err); } } diff --git a/src/utils/debug.ts b/src/utils/debug.ts index acbf5f1..a73eb9e 100644 --- a/src/utils/debug.ts +++ b/src/utils/debug.ts @@ -1,7 +1,8 @@ // src/utils/debug.ts // Debug logging utility. // -// Screen output: controlled by LETTA_DEBUG=1 (or LETTA_DEBUG_FILE for a custom path). +// Screen output: controlled by LETTA_DEBUG=1 (or DEBUG=1 for legacy compatibility), +// or LETTA_DEBUG_FILE for a custom path. // File output: always written to ~/.letta/logs/debug/{agent-id}/{session-id}.log // once debugLogFile.init() has been called. Before init, lines are // silently dropped (no file path yet). @@ -23,12 +24,18 @@ import { format } from "node:util"; // --------------------------------------------------------------------------- /** - * Check if debug mode is enabled via LETTA_DEBUG env var - * Set LETTA_DEBUG=1 or LETTA_DEBUG=true to enable debug logging + * Check if debug mode is enabled via LETTA_DEBUG env var. + * Also accepts DEBUG=1|true for legacy compatibility. */ export function isDebugEnabled(): boolean { - const debug = process.env.LETTA_DEBUG; - return debug === "1" || debug === "true"; + const lettaDebug = process.env.LETTA_DEBUG; + const legacyDebug = process.env.DEBUG; + return ( + lettaDebug === "1" || + lettaDebug === "true" || + legacyDebug === "1" || + legacyDebug === "true" + ); } function getDebugFile(): string | null { diff --git a/src/websocket/listen-client.ts b/src/websocket/listen-client.ts index d6336dc..bc32a56 100644 --- a/src/websocket/listen-client.ts +++ b/src/websocket/listen-client.ts @@ -84,6 +84,7 @@ import type { TranscriptBackfillMessage, TranscriptSupplementMessage, } from "../types/protocol"; +import { isDebugEnabled } from "../utils/debug"; import { getListenerBlockedReason } from "./helpers/listenerQueueAdapter"; import { handleTerminalInput, @@ -458,7 +459,7 @@ function handleModeChange(msg: ModeChangeMessage, socket: WebSocket): void { success: true, }); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log(`[Listen] Mode changed to: ${msg.mode}`); } } catch (error) { @@ -470,7 +471,7 @@ function handleModeChange(msg: ModeChangeMessage, socket: WebSocket): void { error: error instanceof Error ? error.message : "Mode change failed", }); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error("[Listen] Mode change failed:", error); } } @@ -1325,7 +1326,7 @@ function scheduleQueuePump( }) .catch((error: unknown) => { runtime.queuePumpScheduled = false; - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error("[Listen] Error in queue pump:", error); } opts.onStatusChange?.("idle", opts.connectionId); @@ -2115,7 +2116,7 @@ function populateInterruptQueue( return true; } - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.warn( "[Listen] Cancel during approval loop but no tool_call_ids available " + "for interrupted queue — next turn may hit pre-stream conflict. " + @@ -2849,7 +2850,7 @@ async function connectWithRetry( raw, }); } - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log( `[Listen] Received message: ${JSON.stringify(parsed, null, 2)}`, ); @@ -3075,7 +3076,7 @@ async function connectWithRetry( ); }) .catch((error: unknown) => { - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error("[Listen] Error handling queued get_state:", error); } }); @@ -3114,7 +3115,7 @@ async function connectWithRetry( } }) .catch((error: unknown) => { - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error( "[Listen] Error handling queued pending approval recovery:", error, @@ -3187,7 +3188,7 @@ async function connectWithRetry( scheduleQueuePump(runtime, socket, opts); }) .catch((error: unknown) => { - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error("[Listen] Error handling queued message:", error); } opts.onStatusChange?.("idle", opts.connectionId); @@ -3212,7 +3213,7 @@ async function connectWithRetry( runtime.queuedMessagesByItemId.clear(); runtime.queueRuntime.clear("shutdown"); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log( `[Listen] WebSocket disconnected (code: ${code}, reason: ${reason.toString()})`, ); @@ -3230,7 +3231,7 @@ async function connectWithRetry( // 1008: Environment not found - need to re-register if (code === 1008) { - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log("[Listen] Environment not found, re-registering..."); } // Stop retry loop and signal that we need to re-register @@ -3261,7 +3262,7 @@ async function connectWithRetry( type: "_ws_error", message: error.message, }); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error("[Listen] WebSocket error:", error); } // Error triggers close(), which handles retry logic. @@ -3320,7 +3321,7 @@ async function handleIncomingMessage( return; } - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.log( `[Listen] Handling message: agentId=${agentId}, requestedConversationId=${requestedConversationId}, conversationId=${conversationId}`, ); @@ -4048,7 +4049,7 @@ async function handleIncomingMessage( stopReason: "error", }); - if (process.env.DEBUG) { + if (isDebugEnabled()) { console.error("[Listen] Error handling message:", error); } } finally {