From 34505d2379eb29dfa1efefbc4f292d821d63b1b2 Mon Sep 17 00:00:00 2001 From: Jacek Tomaszewski Date: Wed, 25 Mar 2026 09:19:15 +0100 Subject: [PATCH 1/2] dont reply endlessly if claude fails? (vibe-kanban 58f8151b) see https://fullstackhouse.slack.com/archives/C0ALT5X0XD4/p1774425431551679 --- package-lock.json | 4 ++-- src/cli.ts | 10 +++++++++- src/config.ts | 1 + src/platforms/slack.ts | 21 ++++++++++++++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4025653..84a5ed4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@fullstackhouse/agentloop", - "version": "0.7.1", + "version": "0.7.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@fullstackhouse/agentloop", - "version": "0.7.1", + "version": "0.7.2", "license": "MIT", "dependencies": { "dotenv": "^16.4.7", diff --git a/src/cli.ts b/src/cli.ts index 37fd5f3..b8e2bd1 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -110,7 +110,15 @@ async function serve(options: CliOptions): Promise { } const slackApi = new SlackApi(xoxc, xoxd); - const adapter = new SlackAdapter(agent, slackApi, slackChannels, slackChannelBlacklist); + const adapter = new SlackAdapter( + agent, + slackApi, + slackChannels, + slackChannelBlacklist, + config.slackUsers, + 10_000, // pollIntervalMs + config.maxRetries, + ); await adapter.start(); adapters.push(adapter); } diff --git a/src/config.ts b/src/config.ts index 524893f..5b16317 100644 --- a/src/config.ts +++ b/src/config.ts @@ -37,6 +37,7 @@ export interface AppConfig { slackChannels?: string[]; slackChannelBlacklist?: string[]; slackUsers?: string[]; + maxRetries?: number; // Max retries before giving up on a message (default: 3) workspaceDir?: string; // Working directory for Claude Code subprocess mcpServers?: Record; } diff --git a/src/platforms/slack.ts b/src/platforms/slack.ts index 7f743b5..24a98d2 100644 --- a/src/platforms/slack.ts +++ b/src/platforms/slack.ts @@ -14,6 +14,8 @@ export class SlackAdapter { private threadSessions = new Map(); /** Cache of user IDs to display names */ private userNames = new Map(); + /** Tracks retry attempts per message */ + private retryCount = new Map(); constructor( private agent: Agent, @@ -22,6 +24,7 @@ export class SlackAdapter { private channelBlacklist?: string[], private users?: string[], private pollIntervalMs = 10_000, + private maxRetries = 3, ) {} async start(): Promise { @@ -262,11 +265,25 @@ ${cleanText} await this.slackApi.reactionsRemove(channel, msg.ts, 'eyes').catch(this.ignoreSlackError('no_reaction')); await this.slackApi.reactionsAdd(channel, msg.ts, 'white_check_mark').catch(this.ignoreSlackError('already_reacted')); + // Clear retry counter on success + this.retryCount.delete(key); + const elapsed = Date.now() - startTime; console.log(`[slack] ${key}: Completed successfully in ${elapsed}ms`); } catch (e) { const elapsed = Date.now() - startTime; - console.error(`[slack] ${key}: Failed after ${elapsed}ms:`, e); + const attempt = (this.retryCount.get(key) || 0) + 1; + this.retryCount.set(key, attempt); + + if (attempt < this.maxRetries) { + console.error(`[slack] ${key}: Failed after ${elapsed}ms (attempt ${attempt}/${this.maxRetries}), will retry:`, e); + // Don't add ✅, let next poll retry + return; + } + + console.error(`[slack] ${key}: Failed after ${elapsed}ms (attempt ${attempt}/${this.maxRetries}, giving up):`, e); + this.retryCount.delete(key); + try { const threadTs = msg.thread_ts || msg.ts; console.log(`[slack] ${key}: Notifying user of error`); @@ -274,6 +291,8 @@ ${cleanText} await this.slackApi.reactionsRemove(channel, msg.ts, 'eyes').catch((e) => { console.warn(`[slack] ${key}: Failed to remove eyes reaction:`, e); }); + // Add ✅ after max retries to prevent endless retry loop + await this.slackApi.reactionsAdd(channel, msg.ts, 'white_check_mark').catch(this.ignoreSlackError('already_reacted')); console.log(`[slack] ${key}: Error notification sent`); } catch (e) { console.warn(`[slack] ${key}: Failed to notify user of error:`, e); From 4e30363418c9239d442dfae6dc8d45cee52c61f4 Mon Sep 17 00:00:00 2001 From: Jacek Tomaszewski Date: Wed, 25 Mar 2026 09:20:03 +0100 Subject: [PATCH 2/2] chore: bump version to 0.7.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 84a5ed4..aa0b39b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@fullstackhouse/agentloop", - "version": "0.7.2", + "version": "0.7.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@fullstackhouse/agentloop", - "version": "0.7.2", + "version": "0.7.3", "license": "MIT", "dependencies": { "dotenv": "^16.4.7", diff --git a/package.json b/package.json index 9908475..9c1b081 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fullstackhouse/agentloop", - "version": "0.7.2", + "version": "0.7.3", "description": "AI agent that monitors chat platforms and responds using Claude Code", "repository": { "type": "git",