Skip to content

Conversation

@shuhuiluo
Copy link
Collaborator

@shuhuiluo shuhuiluo commented Dec 9, 2025

PR comments (fired as issue_comment events) now respect branch filters.

  • Add in-memory PR branch cache with timestamp ordering
  • Cache populated from PR/review/review_comment events
  • API fallback on cache miss via installation Octokit
  • Lazy TTL (24h) + delete-on-close for garbage collection
  • Support retroactive anchor creation when PR base branch changes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Installation-scoped GitHub API operations are now supported.
    • PR base-branch info is cached with a 24-hour TTL to improve PR-related performance and reduce lookups and enable branch-aware filtering.
    • PR comment handling now attempts to resolve branch context for more accurate processing.
    • Message editing now supports retroactive anchor creation when an edit targets a missing anchor.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Walkthrough

Adds a new public method getInstallationOctokit(installationId: number) to GitHubApp. Refactors EventProcessor to accept a GitHubApp instance and adds an in-memory PR base-branch cache with a 24‑hour TTL plus cache-management helpers (updateBranchCache, deleteBranchCache, getCachedBranch, fetchPrBranch). PR- and comment-related event handlers updated to read/update/delete the cache and to fall back to API fetch when needed; branch information is threaded into event processing. index.ts wiring updated to pass githubApp into EventProcessor. MessageDeliveryService.handleEdit now creates a retroactive anchor when an edit has no existing message and the context is an anchor.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review cache correctness (TTL enforcement, staleness checks, update semantics) in src/github-app/event-processor.ts
  • Verify fetchPrBranch uses GitHubApp.getInstallationOctokit correctly and handles API errors/timeouts
  • Inspect updated event handlers (onPullRequest, onIssueComment, onPullRequestReview, onPullRequestReviewComment) for consistent cache lifecycle and concurrency concerns
  • Confirm constructor/signature changes are propagated (src/index.ts) and TypeScript imports/types align
  • Validate retroactive anchor flow in src/services/message-delivery-service.ts for unintended side effects

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the primary change: adding branch filtering capability for PR comments. It directly reflects the main objective stated in the PR description and is supported by the code changes across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/pr-comment-branch-filtering

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/github-app/event-processor.ts (2)

43-50: Consider adding a cache size limit to prevent unbounded growth.

The in-memory cache only evicts entries via lazy TTL in getCachedBranch or explicit deletion on PR close. If many PRs are opened but getCachedBranch is never called (e.g., no issue_comment events), entries accumulate indefinitely.

Consider adding an LRU eviction policy or periodic cleanup:

+const MAX_CACHE_SIZE = 10000;
+
 /** Lazy TTL for cache entries (24 hours) */
 const CACHE_TTL_MS = 24 * 60 * 60 * 1000;

Then in updateBranchCache:

   private updateBranchCache(...): void {
     const key = `${repo}#${prNumber}`;
     const existing = this.prBranchCache.get(key);
     if (!existing || updatedAt >= existing.updatedAt) {
       this.prBranchCache.set(key, { branch, updatedAt });
+      // Simple eviction: if over limit, delete oldest entries
+      if (this.prBranchCache.size > MAX_CACHE_SIZE) {
+        const oldest = [...this.prBranchCache.entries()]
+          .sort((a, b) => a[1].updatedAt.getTime() - b[1].updatedAt.getTime())
+          .slice(0, Math.floor(MAX_CACHE_SIZE * 0.1));
+        oldest.forEach(([k]) => this.prBranchCache.delete(k));
+      }
     }
   }

Also applies to: 59-60


349-362: Consider logging when branch cannot be resolved for PR comments.

If the cache misses and installation.id is absent, branch filtering is silently skipped for PR comments. This could cause comments to be delivered to channels with branch filters that should have excluded them.

     if (isPrComment) {
       branch =
         this.getCachedBranch(repository.full_name, issue.number) ?? undefined;
       if (!branch && event.installation?.id) {
         branch =
           (await this.fetchPrBranch(
             repository.full_name,
             issue.number,
             event.installation.id
           )) ?? undefined;
       }
+      if (!branch) {
+        console.warn(
+          `Could not resolve branch for PR comment on ${repository.full_name}#${issue.number}, skipping branch filter`
+        );
+      }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1bc0a and d9f21b3.

📒 Files selected for processing (4)
  • src/github-app/app.ts (1 hunks)
  • src/github-app/event-processor.ts (8 hunks)
  • src/index.ts (1 hunks)
  • src/services/message-delivery-service.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Store context externally - maintain stateless bot architecture with no message history, thread context, or conversation memory
Use <@{userId}> for mentions in messages AND add mentions in sendMessage options - do not use @username format
Implement event handlers for onMessage, onSlashCommand, onReaction, onTip, and onInteractionResponse to respond to Towns Protocol events
Define slash commands in src/commands.ts as a const array with name and description properties, then register handlers using bot.onSlashCommand()
Set ID in interaction requests and match ID in responses to correlate form submissions, button clicks, and transaction/signature responses
Use readContract for reading smart contract state, writeContract for SimpleAccount operations, and execute() for external contract interactions
Fund bot.appAddress (Smart Account) for on-chain operations, not bot.botId (Gas Wallet/EOA)
Use bot.* handler methods directly (outside event handlers) for unprompted messages via webhooks, timers, or tasks - requires channelId, spaceId, or other context stored externally
Always check permissions using handler.hasAdminPermission() before performing admin operations like ban, redact, or pin
User IDs are hex addresses in format 0x..., not usernames - use them consistently throughout event handling and message sending
Slash commands do not trigger onMessage - register slash command handlers using bot.onSlashCommand() instead
Use getSmartAccountFromUserId() to retrieve a user's wallet address from their userId
Include required environment variables: APP_PRIVATE_DATA (bot credentials) and JWT_SECRET (webhook security token)

Files:

  • src/services/message-delivery-service.ts
  • src/github-app/app.ts
  • src/index.ts
  • src/github-app/event-processor.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Provide alt text for image attachments and use appropriate MIME types for chunked attachments (videos, screenshots)

Files:

  • src/services/message-delivery-service.ts
  • src/github-app/app.ts
  • src/index.ts
  • src/github-app/event-processor.ts
🧬 Code graph analysis (1)
src/github-app/event-processor.ts (2)
src/github-app/app.ts (1)
  • GitHubApp (11-82)
src/formatters/webhook-events.ts (1)
  • formatIssueComment (162-192)
🔇 Additional comments (7)
src/services/message-delivery-service.ts (1)

183-199: LGTM! Retroactive anchor creation logic is well-structured.

The fallback to handleCreate when editing an anchor without an existing message enables proper handling of PRs whose base branch changes to match a filter. The early return prevents the misleading "No existing message to edit" log.

src/github-app/app.ts (1)

74-81: LGTM! Clean delegation to underlying Octokit App.

The method correctly exposes installation-scoped Octokit access needed for the PR branch API fallback.

src/index.ts (1)

56-60: LGTM! Dependency injection updated correctly.

The githubApp instance is properly passed as the first argument to EventProcessor, enabling installation-scoped API access for PR branch fetching.

src/github-app/event-processor.ts (4)

242-253: LGTM! Cache lifecycle properly tied to PR state.

Deleting on close and updating on other actions ensures the cache stays relevant. Using pull_request.updated_at maintains timestamp ordering consistency.


390-396: LGTM! Cache populated from review events.

Updating the cache during review events ensures branch data is available for subsequent issue_comment events on the same PR.


540-568: LGTM with minor note.

The API fallback is well-implemented with proper error handling and cache population on success. The repo.split("/") assumes valid owner/repo format which is guaranteed by GitHub webhook payloads.


507-537: LGTM! Cache utilities are correctly implemented.

The >= comparison allows idempotent updates, and lazy TTL eviction in getCachedBranch ensures stale entries don't persist across lookups.

@shuhuiluo shuhuiluo force-pushed the feat/pr-comment-branch-filtering branch from d9f21b3 to 14d3e3a Compare December 9, 2025 08:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/github-app/event-processor.ts (2)

335-385: Verify interaction between PR-comment branch resolution and BRANCH_FILTERABLE_EVENTS_SET

The PR-comment path (cache → API fallback → optional branch filter, with a graceful “skip branch filter” fallback when unresolved) is a good match for the stated objective.

One thing to double‑check: if "comments" is ever added to BRANCH_FILTERABLE_EVENTS_SET, non‑PR issue comments will still hit processEvent with eventType: "comments" and branch undefined, which would trigger the "branch-filterable but no branch provided" error. If you do plan to make "comments" officially branch‑filterable at some point, consider:

  • Passing a branch for issue comments as well (e.g., default branch), or
  • Special‑casing "comments" in the isBranchFilterable check based on isPrComment.

As implemented, this is fine as long as "comments" stays out of BRANCH_FILTERABLE_EVENTS_SET.


545-573: Minor hardening for fetchPrBranch input and error handling

The fallback fetch via installation‑scoped Octokit and subsequent cache update look correct. Two small, optional robustness tweaks you might consider:

  • Guard the repo.split("/") destructuring in case repo is ever malformed (e.g., log and return null if owner or repoName are missing).
  • Optionally differentiate expected errors (e.g., 404 if the PR was deleted or the installation lost access) from unexpected ones to avoid noisy warnings in benign cases.

These aren’t blocking, but would make this path a bit more defensive in production.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9f21b3 and 14d3e3a.

📒 Files selected for processing (4)
  • src/github-app/app.ts (1 hunks)
  • src/github-app/event-processor.ts (8 hunks)
  • src/index.ts (1 hunks)
  • src/services/message-delivery-service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/services/message-delivery-service.ts
  • src/index.ts
  • src/github-app/app.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Store context externally - maintain stateless bot architecture with no message history, thread context, or conversation memory
Use <@{userId}> for mentions in messages AND add mentions in sendMessage options - do not use @username format
Implement event handlers for onMessage, onSlashCommand, onReaction, onTip, and onInteractionResponse to respond to Towns Protocol events
Define slash commands in src/commands.ts as a const array with name and description properties, then register handlers using bot.onSlashCommand()
Set ID in interaction requests and match ID in responses to correlate form submissions, button clicks, and transaction/signature responses
Use readContract for reading smart contract state, writeContract for SimpleAccount operations, and execute() for external contract interactions
Fund bot.appAddress (Smart Account) for on-chain operations, not bot.botId (Gas Wallet/EOA)
Use bot.* handler methods directly (outside event handlers) for unprompted messages via webhooks, timers, or tasks - requires channelId, spaceId, or other context stored externally
Always check permissions using handler.hasAdminPermission() before performing admin operations like ban, redact, or pin
User IDs are hex addresses in format 0x..., not usernames - use them consistently throughout event handling and message sending
Slash commands do not trigger onMessage - register slash command handlers using bot.onSlashCommand() instead
Use getSmartAccountFromUserId() to retrieve a user's wallet address from their userId
Include required environment variables: APP_PRIVATE_DATA (bot credentials) and JWT_SECRET (webhook security token)

Files:

  • src/github-app/event-processor.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Provide alt text for image attachments and use appropriate MIME types for chunked attachments (videos, screenshots)

Files:

  • src/github-app/event-processor.ts
🧬 Code graph analysis (1)
src/github-app/event-processor.ts (2)
src/github-app/app.ts (1)
  • GitHubApp (11-82)
src/formatters/webhook-events.ts (1)
  • formatIssueComment (162-192)
🔇 Additional comments (2)
src/github-app/event-processor.ts (2)

41-51: PR branch cache shape and TTL logic look sound

The in‑memory cache structure (PrBranchCacheEntry, prBranchCache, 24h TTL) and helper methods (updateBranchCache, deleteBranchCache, getCachedBranch) look correct and align with the described behavior (last‑writer‑wins via updatedAt and lazy eviction). No functional issues stand out here.

Also applies to: 59-61, 512-543


240-266: Cache updates on PR / review / review-comment events are consistent

Using pull_request.base.ref plus pull_request.updated_at to drive updateBranchCache (and deleteBranchCache on closed) is consistent across PR, review, and review-comment handlers and should give correct ordering even if events arrive slightly out of order. This wiring matches the intended “timestamp-ordered” cache semantics.

Also applies to: 392-402, 430-440

PR comments (fired as issue_comment events) now respect branch filters.

- Add in-memory PR branch cache with timestamp ordering
- Cache populated from PR/review/review_comment events
- API fallback on cache miss via installation Octokit
- Lazy TTL (24h) + delete-on-close for garbage collection
- Support retroactive anchor creation when PR base branch changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@shuhuiluo shuhuiluo force-pushed the feat/pr-comment-branch-filtering branch from 14d3e3a to d6cb70d Compare December 9, 2025 08:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/github-app/event-processor.ts (1)

349-367: PR comment branch resolution behavior is sensible but could be stricter if desired

The flow for PR comments—cache lookup, then API fallback via installation Octokit, then logging and proceeding without a branch on failure—cleanly enables branch filtering when possible while degrading to pre‑existing behavior (no branch filtering) when the branch cannot be resolved.

If you want “branch filters are always respected” to be a hard guarantee rather than best effort, you might instead choose to drop delivery when branch remains unresolved (or at least skip delivery to channels with a restrictive branchFilter), rather than logging and sending as if unfiltered. Current behavior is fine and backwards‑compatible; this would just tighten semantics.

Also applies to: 369-383

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14d3e3a and d6cb70d.

📒 Files selected for processing (5)
  • src/constants.ts (1 hunks)
  • src/github-app/app.ts (1 hunks)
  • src/github-app/event-processor.ts (8 hunks)
  • src/index.ts (1 hunks)
  • src/services/message-delivery-service.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/constants.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/services/message-delivery-service.ts
  • src/github-app/app.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Store context externally - maintain stateless bot architecture with no message history, thread context, or conversation memory
Use <@{userId}> for mentions in messages AND add mentions in sendMessage options - do not use @username format
Implement event handlers for onMessage, onSlashCommand, onReaction, onTip, and onInteractionResponse to respond to Towns Protocol events
Define slash commands in src/commands.ts as a const array with name and description properties, then register handlers using bot.onSlashCommand()
Set ID in interaction requests and match ID in responses to correlate form submissions, button clicks, and transaction/signature responses
Use readContract for reading smart contract state, writeContract for SimpleAccount operations, and execute() for external contract interactions
Fund bot.appAddress (Smart Account) for on-chain operations, not bot.botId (Gas Wallet/EOA)
Use bot.* handler methods directly (outside event handlers) for unprompted messages via webhooks, timers, or tasks - requires channelId, spaceId, or other context stored externally
Always check permissions using handler.hasAdminPermission() before performing admin operations like ban, redact, or pin
User IDs are hex addresses in format 0x..., not usernames - use them consistently throughout event handling and message sending
Slash commands do not trigger onMessage - register slash command handlers using bot.onSlashCommand() instead
Use getSmartAccountFromUserId() to retrieve a user's wallet address from their userId
Include required environment variables: APP_PRIVATE_DATA (bot credentials) and JWT_SECRET (webhook security token)

Files:

  • src/github-app/event-processor.ts
  • src/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Provide alt text for image attachments and use appropriate MIME types for chunked attachments (videos, screenshots)

Files:

  • src/github-app/event-processor.ts
  • src/index.ts
🧬 Code graph analysis (1)
src/github-app/event-processor.ts (1)
src/github-app/app.ts (1)
  • GitHubApp (11-82)
🔇 Additional comments (5)
src/index.ts (1)

56-60: EventProcessor wiring with githubApp matches new constructor

Passing githubApp as the first argument aligns with the updated EventProcessor signature (githubApp, subscriptionService, messageDeliveryService); no issues spotted.

src/github-app/event-processor.ts (4)

41-50: Branch cache state and GitHubApp injection look consistent

The PrBranchCacheEntry structure, 24‑hour CACHE_TTL_MS, and prBranchCache Map are straightforward, and injecting GitHubApp via the constructor cleanly enables the API fallback without leaking that concern into callers. No correctness issues apparent.

Also applies to: 59-66


243-254: PR handler keeps cache in sync with PR lifecycle

Updating the cache on all non‑closed actions and deleting it on closed ensures you don’t retain stale branches while still having entries for active PRs. Using pull_request.updated_at to monotically gate updates is reasonable and guards against out‑of‑order events.


395-402: Keeping cache warm on reviews and review comments is a good touch

Refreshing the cache on onPullRequestReview and onPullRequestReviewComment ensures the base branch stays correct even if those events arrive without a preceding PR webhook in the same process lifetime. This also helps avoid unnecessary API calls later for PR comments.

Also applies to: 433-440


512-573: Cache helper and API fallback implementations look robust

The updateBranchCache/deleteBranchCache/getCachedBranch trio correctly handles monotonic updates and TTL expiry, and fetchPrBranch uses a single repo‑scoped key plus defensive error handling and logging. The lazy TTL cleanup on access is simple and adequate for the current scale; you can always layer explicit metrics or periodic pruning later if needed.

@shuhuiluo shuhuiluo merged commit b6540f7 into main Dec 9, 2025
2 checks passed
@shuhuiluo shuhuiluo deleted the feat/pr-comment-branch-filtering branch December 9, 2025 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants