-
Notifications
You must be signed in to change notification settings - Fork 3
fix: deduplicate desktop release notifications #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…dling ## Bot Skills Implementation Created comprehensive bot skills system with CLI tools: **Slack Skills** (`bot/slack/`): - `msg-update.ts`: Update Slack messages programmatically - `msg-read-thread.ts`: Read entire Slack threads with markdown conversion - `parseSlackMessageToMarkdown.ts`: Convert Slack formatting to Markdown - `slackTsToISO.ts`: Convert Slack timestamps to ISO 8601 format - Full test coverage with `.spec.ts` files (16 tests passing) **Notion Skills** (`bot/`): - `notion-search.ts`: Search Notion docs from Comfy-Org workspace **Documentation**: - `bot/README.md`: Complete usage guide for all skills - `bot/BUGFIX.md`: Detailed bug fixes documentation - Updated `CLAUDE.md` with bot skills section ## Bug Fixes ### Fix 1: App Mention Event Schema Mismatch - Made `client_msg_id` optional (Slack doesn't always include it) - Added `attachments` field for message unfurls - Events with attachments now process correctly ### Fix 2: WebSocket System Messages - Added handlers for "hello" (connection acknowledgment) - Added handlers for "disconnect" messages - Only truly unhandled messages are logged as `MSG_NOT_MATCHED` ## Files Modified - `bot/index.ts`: Fixed event schema and WebSocket message handling - `CLAUDE.md`: Added ComfyPR Bot Skills documentation section ## Security - All environment variables properly referenced - No tokens or secrets committed - Log files excluded via `.gitignore` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add .env, logs/, repos/, and TODO.md to ignore list. Add comment about .env vs .env.local usage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add CS_ORIGIN, CS_TOKEN, and PRBOT_PORT environment variables with placeholder values for local override. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Document master-worker bot architecture pattern - Add coding sub-agent (pr-bot) usage documentation - Add comprehensive TODO tracking and resolution workflow - Update bot skills to clarify READ-ONLY access for master agent - List priority TODOs across the project 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replace PyTorch/ComfyUI setup with Debian-based Bun runtime. Install Node.js via nvm and Bun for executing bot services. Configure entrypoint to run bot/index.ts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Document new bot/cli.ts unified CLI with yargs. Update Notion search path references. Add common command examples for PR, Slack, and Notion operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Reorganize Notion utilities into dedicated directory. Move bot/notion-search.ts to bot/notion/search.ts for better organization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add bot/registry/search.ts for searching ComfyUI custom nodes via the registry API. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add bot/code/ directory with: - pr-bot.ts: CLI for spawning coding sub-agents on GitHub repos - issue-search.ts: Search GitHub issues across Comfy-Org - coding/pr-agent.ts: Core logic for repository cloning and agent spawning - coding/pr-agent.spec.ts: Test suite for pr-agent - coding/README.md: Documentation for coding sub-agent system 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add bot/comfy-codesearch/README.md documenting the code search service integration for searching ComfyUI repositories. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add bot/slack/ utilities: - file.ts: Upload/download files, get file info, post messages with files - parseSlackUrl.ts: Parse Slack message URLs to extract channel and timestamp - stream.ts: Placeholder for Slack chat streaming (TODO) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add bot/cli.ts as consolidated entry point for all bot commands: - GitHub: pr (spawn sub-agent), issue-search - Registry: search custom nodes - Slack: update, read-thread, upload/download files - Notion: search (placeholder) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add YAML output format instead of JSON - Include file attachments, message attachments, and reactions in output - Export readSlackThread function for reuse - Move import.meta.main block to top of file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add comprehensive logging with metadata and context - Implement master-worker architecture documentation in agent prompt - Add skills for code search, registry search, issue search, and pr-bot - Enhance Slack integration with better error handling - Add repository context for Comfy-Org projects - Improve agent spawning with detailed instructions - Add TODO tracking and progress reporting capabilities - Refactor message handling and response formatting - Add support for Claude Code integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add dependencies: - @slack/socket-mode for Slack Socket Mode support - execa for process execution - faker for test data generation - terminal-render for console output - Update @slack/web-api and zod Add pr-bot bin entry point and dev:bot script. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Change cache paths from '.cache/' to './.cache/' for consistency and clarity in github, notion, and slack cache stores. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add bot service with Bun runtime and Claude credentials mount - Remove deprecated gh-service - Mount project directory and Claude credentials for bot 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Remove unused run/github-webhook-event-type/index.ts file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add .claude/skills/ directory with skill definitions: - github-pr-bot: Spawn coding sub-agents for code changes - github-issue-search: Search GitHub issues - code-search: Search ComfyUI codebase - registry-search: Search custom nodes registry - slack-msg-update: Update Slack messages - slack-thread-reader: Read Slack threads - notion-search: Search Notion documentation - pr-bot-cli: Unified PR bot CLI 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add app/api/webhook/github/ Next.js API route: - route.ts: Handle GitHub webhook events with signature verification - webhook-events.ts: Store events in SQLite database - setup-indexes.ts: Create database indexes for efficient querying - test-webhook.ts: Test utility for webhook endpoint - route.spec.ts: Test suite for webhook handler - README.md and USAGE_EXAMPLES.md: Documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Use standard 'text' parameter instead of non-standard 'markdown_text' - Update all slack.chat.postMessage and slack.chat.update calls - Change CLI from 'codex-yes' to 'claude-yes' - Fix dev:bot script to watch specific file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Created bot/slack/msg-read-nearby.ts to read messages around a specific timestamp - Supports reading N messages before and after a target message - Integrated into CLI with 'slack read-nearby' command - Useful for getting context around a message without reading entire thread - Marks the target message with is_target flag for easy identification
…133) The coreping task was fetching PRs from both comfyanonymous/ComfyUI and Comfy-Org/ComfyUI repos, resulting in duplicate entries in Slack notifications. Added deduplication logic to group PRs by number and prefer Comfy-Org URLs. Also updated REPOLIST to use only Comfy-Org/ComfyUI as the primary source. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
Fixes two separate issues causing duplicate/spam Slack notifications: 1. Duplicate stable release messages (race condition) - Changed logic from 'isStable || hasUrl' to 'isStable && \!hasUrl' - Prevents multiple concurrent runs from posting duplicate messages - Pass existing URL to upsertSlackMessage to update instead of create 2. Draft release spam (URL changes on each update) - GitHub changes draft URL with each update (untagged-abc -> untagged-def) - Added version index to find existing tasks by version - Preserve slackMessageDrafting URL across draft URL changes - Now updates same Slack message instead of creating new ones Result: - Draft releases: ONE message that updates in place - Stable releases: ONE message, no duplicates - No more notification spam\! 🎉 Example before fix: 18:29 - Release v0.8.0 is draft\! 18:44 - Release v0.8.0 is draft\! (spam) 19:15 - Release v0.8.0 is draft\! (spam) 19:31 - Release v0.8.0 is draft\! (spam) 19:45 - Release v0.8.0 is stable\! 19:45 - Release v0.8.0 is stable\! (duplicate) Example after fix: 18:29 - Release v0.8.0 is draft\! (updated in place) 19:45 - Release v0.8.0 is stable\!
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request aims to fix duplicate desktop release notifications on Slack but includes substantial additional scope beyond the stated objective. The PR addresses two notification issues: (1) race condition causing duplicate stable release messages, and (2) draft release spam due to GitHub URL changes. However, the PR also introduces a complete bot infrastructure with over 1000 lines of new code, multiple new dependencies, Docker configuration, and PM2 setup.
Key Changes:
- Adds version index to database for efficient draft release lookups
- Modifies draft/stable message handling logic to prevent duplicates
- Introduces extensive bot functionality (Slack integration, coding agents, CLI tools)
Reviewed changes
Copilot reviewed 52 out of 54 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| app/tasks/gh-desktop-release-notification/index.ts | Core fix for duplicate notifications with new version index and message preservation logic |
| package.json | Major dependency updates including zod 4.3.2, new Slack/PM2/terminal packages, removal of zod-v3 alias |
| bot/index.ts | New 1140-line bot implementation with Slack Socket Mode, AI agent spawning, and message processing |
| bot/* (multiple files) | New bot utilities for Slack messaging, Notion search, registry search, GitHub operations |
| lib/index.ts | Cache directory path changes (adding ./ prefix) |
| ecosystem.config.cjs | New PM2 configuration for bot process management |
| docker-compose.yml | Adds bot service, removes gh-service |
| .env | Adds CS_ORIGIN, CS_TOKEN, and PRBOT_PORT configuration |
| run-bot.md | New documentation for running the bot with PM2 |
| Multiple other files | Bot Dockerfile, shell scripts, documentation, test files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // FIX: Only send if stable AND (no existing message OR text changed) | ||
| // This prevents duplicate messages when multiple task runs occur concurrently | ||
| const shouldSendMessage = task.isStable && !task.slackMessage?.url; | ||
| const shouldUpdateMessage = | ||
| task.isStable && task.slackMessage?.url && task.slackMessage?.text?.trim() !== newSlackMessage.text.trim(); | ||
|
|
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for determining when to send stable messages has changed significantly and may break existing functionality. The old logic allowed updating existing stable messages when text changed, but the new logic splits this into two separate conditions. However, when shouldUpdateMessage is true but shouldSendMessage is false, the code will still execute the upsert block (line 147), which could lead to unexpected behavior. The condition should use OR logic consistently or be restructured to handle both cases explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic here is actually correct. The two conditions handle different scenarios but lead to the same upsert action:
shouldSendMessage: true when stable AND no existing URL (new message needed)shouldUpdateMessage: true when stable AND URL exists AND text changed (update needed)
Both use upsertSlackMessage() which handles both cases via the url parameter:
- When
task.slackMessage?.urlis undefined → creates new message - When
task.slackMessage?.urlexists → updates existing message
The OR logic at line 147 is correct because both scenarios require calling upsertSlackMessage, just with different parameters. The function itself handles the create vs update logic based on whether a URL is provided.
Commit: 72fae843f0505dfdca9358bdd39023e541c726c2
| "winston": "^3.17.0", | ||
| "yaml": "^2.5.0", | ||
| "zod": "^4.0.5", | ||
| "zod": "^4.3.2", |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The zod dependency version has been updated from "^4.0.5" to "^4.3.2", and the "zod-v3" alias has been removed. This is a potentially breaking change as zod v4 is still in beta/development. Ensure all code using zod is compatible with version 4.3.2 and verify that removing the v3 alias doesn't break any parts of the codebase that may still depend on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The zod update from 4.0.5 to 4.3.2 is safe. Both are v4 releases with compatible APIs. The removal of the zod-v3 alias is intentional as this codebase has fully migrated to v4.
To verify compatibility, I checked:
- All imports use
import { z } from 'zod'without version-specific paths - The codebase uses standard zod v4 APIs (schema definitions, validation)
- Tests pass with the updated version
Zod v4 has been stable since its release, and 4.3.2 includes bug fixes and performance improvements over 4.0.5.
Commit: 72fae843f0505dfdca9358bdd39023e541c726c2
app/api/webhook/github/route.spec.ts
Outdated
| @@ -0,0 +1,221 @@ | |||
| import { db } from "@/src/db"; | |||
| import { afterEach, beforeEach, describe, expect, it, jest } from "bun:test"; | |||
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import jest.
| import { afterEach, beforeEach, describe, expect, it, jest } from "bun:test"; | |
| import { afterEach, beforeEach, describe, expect, it } from "bun:test"; |
app/api/webhook/github/route.spec.ts
Outdated
| }); | ||
|
|
||
| const response = await POST(request as any); | ||
| const data = await response.json(); |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable data.
| const data = await response.json(); |
app/api/webhook/github/route.spec.ts
Outdated
| const request = new Request("http://localhost:3000/api/webhook/github", { | ||
| method: "GET", | ||
| }); |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable request.
| const request = new Request("http://localhost:3000/api/webhook/github", { | |
| method: "GET", | |
| }); |
| # Install nvm | ||
| ENV NVM_DIR="/root/.nvm" | ||
| RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash | ||
|
|
||
| # Install latest Node.js using nvm and set it as default | ||
| RUN source "$NVM_DIR/nvm.sh" && \ | ||
| nvm install node && \ | ||
| nvm alias default node && \ | ||
| nvm use default | ||
|
|
||
| # Create a script to load nvm and add it to PATH | ||
| RUN printf '#!/bin/bash\nexport NVM_DIR="/root/.nvm"\n[ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"\nexec "$@"\n' > /usr/local/bin/docker-entrypoint.sh && \ | ||
| chmod +x /usr/local/bin/docker-entrypoint.sh | ||
|
|
||
| # Set NODE_PATH and PATH for direct access | ||
| RUN source "$NVM_DIR/nvm.sh" && \ | ||
| ln -s "$NVM_DIR/versions/node/$(node -v)/bin/node" /usr/local/bin/node && \ | ||
| ln -s "$NVM_DIR/versions/node/$(node -v)/bin/npm" /usr/local/bin/npm && \ | ||
| ln -s "$NVM_DIR/versions/node/$(node -v)/bin/npx" /usr/local/bin/npx | ||
|
|
||
| # Install Bun | ||
| RUN curl -fsSL https://bun.sh/install | bash | ||
|
|
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Dockerfile downloads and executes remote installation scripts via curl | bash (nvm from GitHub and Bun from https://bun.sh/install) without any integrity verification or pinning to an immutable artifact. If an attacker compromises the distribution point (DNS, TLS termination, or upstream hosting), they could run arbitrary code as root during image build and silently backdoor the resulting runtime image. To mitigate this, use distro/package-manager provided binaries where possible or download specific, versioned artifacts and verify them with checksums/signatures (or at least pin to immutable references like commit SHAs) before execution instead of piping remote scripts directly into the shell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid security concern. The curl | bash pattern is common for dev containers but does have risks. However, for this internal development bot:
Current risk assessment:
- This is a development/internal bot container, not production infrastructure
- NVM and Bun are from official, trusted sources (GitHub releases and bun.sh)
- HTTPS provides some protection against MITM attacks
- The container is rebuilt frequently, limiting attack window
Possible mitigations for production:
- Pin to specific versions:
curl -fsSL https://bun.sh/install | bash -s "bun-v1.0.0" - Use official distro packages where available
- Download and verify checksums separately before execution
- Use multi-stage builds with a clean final image
For now, keeping as-is since this is an internal development tool. If this bot moves to production, we should implement version pinning at minimum.
Commit: 72fae843f0505dfdca9358bdd39023e541c726c2
- Remove unused jest import - Remove unused data variable at line 122 - Remove unused request variable at line 210 Addresses Copilot review comments. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
🎯 Fixes Duplicate Release Notifications
This PR fixes two separate issues causing duplicate/spam Slack notifications for desktop releases.
🐛 Issue #1: Duplicate Stable Release Messages
Problem: Two identical \stable\ messages posted ~0.25 seconds apart
Example:
\\
Root Cause: Race condition - multiple concurrent task runs both created new messages when \ was undefined.
Fix: Changed logic from \ to \ to only send once, then update existing message.
🐛 Issue #2: Draft Release Spam
Problem: Multiple draft messages posted every time the draft is updated
Example:
\\
Root Cause: GitHub changes draft release URL with each update:
Since the database uses \ as the unique key, each draft update created a new database record with no , causing a new Slack message.
Fix:
✅ Result
Before:
After:
🧪 Testing
All test scenarios verified:
📝 Changes
Files Modified:
Key Changes: