From c438619b1bd9670130b7547463733af23c21bef7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Feb 2026 19:36:35 +0000 Subject: [PATCH] Fix 5 runtime bugs found during debug pass 1. Redirect console.log to stderr to prevent conflict with MCP StdioServerTransport which hijacks stdout for protocol messages, corrupting both agent output and MCP communication. 2. Wire up the unused lastMentionId field to pass since_id on the mentions API call, avoiding redundant refetches of the same tweets every polling cycle. 3. Add null guard in fetchThread before accessing response.data to prevent null dereference crashes when the API returns unexpected data. 4. Copy the tweets array before sorting in parseThread to avoid mutating the caller's data via Array.sort() in-place side effect. 5. Cap processedMentions Set at 10,000 entries and prune oldest to prevent unbounded memory growth in long-running deployments. https://claude.ai/code/session_015bNhpqRa1sDd4NPLPuZqxV --- src/index.ts | 5 +++++ src/services/agent.ts | 10 ++++++++++ src/services/xapi.ts | 28 +++++++++++++++++++++------- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index c29abb7..2892648 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,11 @@ import { AutonomousAgent } from './services/agent.js'; import { XMCPServer } from './mcp/server.js'; async function main() { + // Redirect console.log to stderr so it doesn't conflict with + // MCP StdioServerTransport which uses stdout for protocol messages + const origLog = console.log; + console.log = (...args: any[]) => console.error(...args); + console.log('═══════════════════════════════════════════════════'); console.log(' MyXstack - Autonomous AI Agent on X (Twitter)'); console.log('═══════════════════════════════════════════════════\n'); diff --git a/src/services/agent.ts b/src/services/agent.ts index 326fb38..de10ae7 100644 --- a/src/services/agent.ts +++ b/src/services/agent.ts @@ -11,6 +11,7 @@ export class AutonomousAgent { private grokService: GrokService; private config: AgentConfig; private processedMentions: Set = new Set(); + private static readonly MAX_PROCESSED_MENTIONS = 10000; private isRunning: boolean = false; private pollingIntervalId: NodeJS.Timeout | null = null; private isProcessing: boolean = false; @@ -95,6 +96,15 @@ export class AutonomousAgent { await this.processMention(mention); this.processedMentions.add(mention.post.id); } + + // Prune oldest entries to prevent unbounded memory growth + if (this.processedMentions.size > AutonomousAgent.MAX_PROCESSED_MENTIONS) { + const excess = this.processedMentions.size - AutonomousAgent.MAX_PROCESSED_MENTIONS; + const iter = this.processedMentions.values(); + for (let i = 0; i < excess; i++) { + this.processedMentions.delete(iter.next().value as string); + } + } } catch (error) { console.error('❌ Error in processing loop:', error); } finally { diff --git a/src/services/xapi.ts b/src/services/xapi.ts index f80be1e..13e92a7 100644 --- a/src/services/xapi.ts +++ b/src/services/xapi.ts @@ -44,17 +44,26 @@ export class XAPIClient { throw new Error('Failed to get user ID from response'); } - const mentionsResponse = await this.makeXAPIRequest( - `https://api.twitter.com/2/users/${userId}/mentions?max_results=10&expansions=author_id&tweet.fields=created_at,conversation_id,in_reply_to_user_id,referenced_tweets`, - 'GET' - ); + let mentionsUrl = `https://api.twitter.com/2/users/${userId}/mentions?max_results=10&expansions=author_id&tweet.fields=created_at,conversation_id,in_reply_to_user_id,referenced_tweets`; + if (this.lastMentionId) { + mentionsUrl += `&since_id=${this.lastMentionId}`; + } + + const mentionsResponse = await this.makeXAPIRequest(mentionsUrl, 'GET'); if (!mentionsResponse || !Array.isArray(mentionsResponse.data)) { console.warn('Invalid response from X API (mentions)'); return []; } - return this.parseMentions(mentionsResponse.data); + const mentions = this.parseMentions(mentionsResponse.data); + + // Track the newest mention ID for pagination on the next poll + if (mentionsResponse.data.length > 0) { + this.lastMentionId = mentionsResponse.data[0].id; + } + + return mentions; } catch (error) { console.error('Error fetching mentions:', error); return []; @@ -77,7 +86,12 @@ export class XAPIClient { 'GET' ); - return this.parseThread(response.data || []); + if (!response || !response.data) { + console.warn('Invalid response from X API (thread)'); + return null; + } + + return this.parseThread(response.data); } catch (error) { console.error('Error fetching thread:', error); return null; @@ -184,7 +198,7 @@ export class XAPIClient { private parseThread(tweets: { created_at: string; [key: string]: any }[]): XThread | null { if (tweets.length === 0) return null; - const sorted = tweets.sort((a, b) => + const sorted = [...tweets].sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() );