-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add DM support by removing spaceId from query logic #98
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
Conversation
WalkthroughThis PR introduces support for Direct Messages (DMs) by making Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/services/subscription-service.ts (1)
595-628: Return type mismatch:spaceIdcan be null but is typed asstring.The
getRepoSubscribersreturn type declaresspaceId: string(line 600), butspace_idis nullable in the database schema (defined as nullable for DM channels). This creates a type mismatch:
Return type issue: Change
spaceId: stringtospaceId: string | nullin the Promise return type.Caller issue in event-processor.ts: The method passes
spaceIddirectly tomessageDeliveryService.deliver()(line 135), which expectsspaceId: string(non-nullable inDeliveryParamsinterface). WhenspaceIdis null, this passes a null value to functions expecting a non-null string (includinggetMessageId()which also expects non-nullablestring). The caller must filter out subscriptions with nullspaceIdor updateDeliveryParamsto accept nullable values.Note: polling-service.ts only uses
channelIdfrom the results, so it is unaffected.src/handlers/github-subscription-handler.ts (2)
92-92: Fix ESLint error: unusedspaceIdvariable.The pipeline reports
spaceIdis destructured but never used inhandleSubscribe. Looking at the code flow,spaceIdis used byhandleNewSubscriptionandhandleUpdateSubscriptionwhich receive the fulleventobject and destructurespaceIdthemselves.Proposed fix
async function handleSubscribe( handler: BotHandler, event: SlashCommandEvent, subscriptionService: SubscriptionService, oauthService: GitHubOAuthService, repoArg: string | undefined ): Promise<void> { - const { channelId, spaceId, args } = event; + const { channelId, args } = event;
308-308: Fix ESLint error: unusedspaceIdvariable.The pipeline reports
spaceIdis destructured but never used inhandleUnsubscribe. The called functions (handleRemoveEventTypes,handleFullUnsubscribe) receive the fulleventobject and destructurespaceIdthemselves.Proposed fix
async function handleUnsubscribe( handler: BotHandler, event: SlashCommandEvent, subscriptionService: SubscriptionService, oauthService: GitHubOAuthService, repoArg: string | undefined ): Promise<void> { - const { channelId, spaceId, args } = event; + const { channelId, args } = event;
🤖 Fix all issues with AI agents
In `@CONTRIBUTING.md`:
- Around line 63-70: The Markdown table under the environment variables is
malformed: the header separator row has an extra column separator and the shell
pipeline in the GITHUB_APP_PRIVATE_KEY_BASE64 “How to Get” cell uses an
unescaped pipe which Markdown treats as a column delimiter. Fix by making the
header separator match the three columns used by the rows (ensure exactly three
column separators in the header separator row) and escape the pipe in the
command for GITHUB_APP_PRIVATE_KEY_BASE64 (replace the raw | in `base64 -i
key.pem | tr -d '\n'` with an escaped pipe) so the table renders correctly.
🧹 Nitpick comments (1)
AGENTS.md (1)
113-119: Consider formatting the URL as a proper Markdown link.The bare URL on line 115 should be formatted as a Markdown link for consistency and to satisfy linting rules.
📝 Suggested fix
#### Paid Commands -Add a `paid` property to your command definition with a price in USDC: +Add a `paid` property to your command definition with a [price in USDC](https://hono.dev/docs/guides/best-practices):Or if the URL is not meant to be there (it seems misplaced - it's about Hono best practices, not paid commands):
#### Paid Commands -Add a `paid` property to your command definition with a price in USDC: +Add a `paid` property to your command definition with a price in USDC:Actually, looking more closely, the URL reference to Hono best practices on line 361 makes sense there, but on line 115 it appears to be a documentation artifact that doesn't belong in the Paid Commands section. Please verify if this URL reference should be here or if it was accidentally included.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
AGENTS.mdCONTRIBUTING.mdREADME.mddrizzle/0009_cool_mad_thinker.sqldrizzle/meta/0009_snapshot.jsondrizzle/meta/_journal.jsonpackage.jsonsrc/db/schema.tssrc/handlers/github-subscription-handler.tssrc/routes/oauth-callback.tssrc/services/github-oauth-service.tssrc/services/subscription-service.tssrc/utils/oauth-helpers.tstests/unit/handlers/github-subscription-handler.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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@usernameformat
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/db/schema.tssrc/services/github-oauth-service.tssrc/handlers/github-subscription-handler.tssrc/utils/oauth-helpers.tssrc/routes/oauth-callback.tstests/unit/handlers/github-subscription-handler.test.tssrc/services/subscription-service.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/db/schema.tssrc/services/github-oauth-service.tssrc/handlers/github-subscription-handler.tssrc/utils/oauth-helpers.tssrc/routes/oauth-callback.tstests/unit/handlers/github-subscription-handler.test.tssrc/services/subscription-service.ts
AGENTS.md
📄 CodeRabbit inference engine (CLAUDE.md)
Document agent capabilities, responsibilities, and interfaces in AGENTS.md
Files:
AGENTS.md
🧠 Learnings (9)
📚 Learning: 2025-11-18T23:35:49.436Z
Learnt from: shuhuiluo
Repo: HereNotThere/bot-github PR: 26
File: src/db/index.ts:61-69
Timestamp: 2025-11-18T23:35:49.436Z
Learning: In `webhook_deliveries` table (src/db/index.ts), the `installation_id` column should NOT have a FOREIGN KEY constraint because the table serves as an immutable audit log for idempotency tracking. Records must persist independently even after installations are deleted, and a foreign key would create race conditions when webhooks arrive before installation records are created. The field is intentionally nullable to support webhooks without installation context.
Applied to files:
src/db/schema.tsdrizzle/meta/0009_snapshot.json
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: All event handlers receive a base payload including userId, spaceId, channelId, eventId, and createdAt - use eventId as threadId/replyId when responding to maintain event threading
Applied to files:
src/db/schema.tsAGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Use bot.* handler methods directly (outside event handlers) for unprompted messages via webhooks, timers, or tasks - requires channelId, spaceId, or other context stored externally
Applied to files:
src/handlers/github-subscription-handler.tssrc/utils/oauth-helpers.tsAGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Include required environment variables: APP_PRIVATE_DATA (bot credentials) and JWT_SECRET (webhook security token)
Applied to files:
CONTRIBUTING.mdpackage.json
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Implement event handlers for onMessage, onSlashCommand, onReaction, onTip, and onInteractionResponse to respond to Towns Protocol events
Applied to files:
AGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : User IDs are hex addresses in format 0x..., not usernames - use them consistently throughout event handling and message sending
Applied to files:
AGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Slash commands do not trigger onMessage - register slash command handlers using bot.onSlashCommand() instead
Applied to files:
AGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Define slash commands in src/commands.ts as a const array with name and description properties, then register handlers using bot.onSlashCommand()
Applied to files:
AGENTS.md
📚 Learning: 2025-11-17T23:50:17.552Z
Learnt from: shuhuiluo
Repo: HereNotThere/bot-github PR: 26
File: src/github-app/app.ts:1-3
Timestamp: 2025-11-17T23:50:17.552Z
Learning: The HereNotThere/bot-github project uses Bun as its package manager, with dependencies locked in bun.lock rather than package-lock.json.
Applied to files:
package.json
🧬 Code graph analysis (1)
src/services/subscription-service.ts (1)
src/db/schema.ts (1)
githubSubscriptions(71-110)
🪛 GitHub Actions: CI
src/handlers/github-subscription-handler.ts
[error] 92-92: ESLint: 'spaceId' is assigned a value but never used. (no-unused-vars) | Command: eslint --format unix ./src --max-warnings=0
[error] 308-308: ESLint: 'spaceId' is assigned a value but never used. (no-unused-vars) | Command: eslint --format unix ./src --max-warnings=0
🪛 markdownlint-cli2 (0.18.1)
AGENTS.md
115-115: Bare URL used
(MD034, no-bare-urls)
🔇 Additional comments (44)
drizzle/meta/_journal.json (1)
67-74: LGTM!The new migration journal entry follows the established conventions: sequential
idx, consistentversion, and proper naming pattern for thetag. This correctly tracks the schema migration for makingspaceIdnullable and updating the unique indexes.README.md (1)
164-176: LGTM!The updated documentation clearly explains the new bun-based workflow for local PostgreSQL setup. The note about data persistence via named volume is a helpful addition for developers.
CONTRIBUTING.md (1)
27-29: LGTM!The setup commands are clear and align with the new bun-based workflow defined in package.json.
package.json (3)
3-3: LGTM!Version bump to 1.1.0 is appropriate for this feature addition (DM support).
27-35: LGTM!Dependency updates look reasonable. The Towns Protocol packages are aligned at ^1.0.2.
9-10: Thepostgres:18Docker image tag is valid and available on Docker Hub. No action needed.drizzle/meta/0009_snapshot.json (4)
62-244: LGTM!The
github_subscriptionstable schema correctly reflects the PR objectives:
space_idis now nullable (notNull: false)- Unique index uses
channelId + repoFullNamewithoutspaceId
533-624: LGTM!The
oauth_statestable correctly hasspace_idas nullable to support DM channels during OAuth flows.
625-766: LGTM!The
pending_subscriptionstable correctly hasspace_idas nullable, and the unique index properly useschannelId + repoFullName.
418-532: Verify whether DMs should create message mappings and ifmessage_mappings.space_idneeds to be nullable.The review's observation about other tables is correct—
github_subscriptions,oauth_states, andpending_subscriptionsall have nullablespaceIdfor DM support. However,message_mappings.space_idis defined asnotNull: trueand is part of the composite primary key.All current code paths in
message-delivery-service.tsrequirespaceIdwhen inserting or querying message mappings. No code path found that creates message mappings for DM contexts (wherespaceIdwould be null). If DMs are meant to support message mappings, this would indeed cause insert failures, making the concern valid. If DMs don't need message mappings, the constraint is intentional and correct.src/services/subscription-service.ts (10)
44-51: LGTM!The
SubscribeParamsinterface correctly makesspaceIdoptional and nullable to support DM channels.
229-247: LGTM!The subscription existence check correctly uses only
channelIdandrepoFullName, with a helpful comment explaining that channelId is globally unique in Towns.
250-265: LGTM!The insert correctly handles nullable
spaceIdwith thespaceId ?? nullpattern.
503-516: LGTM!The
unsubscribemethod correctly removesspaceIdfrom its signature and uses onlychannelId + repoFullNamefor the delete operation.
521-555: LGTM!The
getSubscriptionmethod correctly uses onlychannelId + repoFullNamefor lookups.
560-585: LGTM!The
getChannelSubscriptionsmethod correctly queries bychannelIdonly.
383-396: LGTM!The
removeEventTypesmethod correctly callsunsubscribewith the updated signature (withoutspaceId).
930-954: LGTM!The private
requiresInstallationFailuremethod correctly accepts optionalspaceId.
960-988: LGTM!The private
storePendingSubscriptionmethod correctly handles nullablespaceIdwith thespaceId ?? nullpattern.
1018-1030: LGTM!The
validateRepoAccessAndGetSubscriptionmethod correctly uses the updatedgetSubscriptionsignature withoutspaceId.drizzle/0009_cool_mad_thinker.sql (1)
1-7: LGTM!The migration correctly handles the schema transition:
- Drops indexes before modifying columns to avoid constraint violations.
- Makes
space_idnullable across all three tables to support DM channels.- Recreates unique indexes using only
(channel_id, repo_full_name)sincechannelIdis globally unique in Towns.src/db/schema.ts (5)
53-53: LGTM!Making
spaceIdnullable inoauthStateswith a clear inline comment explaining DM channel support.
75-75: LGTM!Consistent nullable
spaceIdforgithubSubscriptionsto support DM channels.
102-106: LGTM!The unique index correctly uses only
(channelId, repoFullName)with a clear comment explaining thatchannelIdis globally unique in Towns, makingspaceIdunnecessary for uniqueness.
212-212: LGTM!Nullable
spaceIdinpendingSubscriptionsfor DM channel support.
225-229: LGTM!The unique index for pending subscriptions mirrors the
githubSubscriptionsindex pattern, using only(channelId, repoFullName).tests/unit/handlers/github-subscription-handler.test.ts (4)
520-525: LGTM!The test correctly expects
removeEventTypesto be called with(userId, channelId, repoFullName, eventTypes)withoutspaceId, matching the updated service signature.
553-553: LGTM!Index adjusted from
[0][3]to[0][2]to correctly accessrepoFullNameafterspaceIdremoval from the call signature.
604-604: LGTM!Consistent index adjustment for the markdown stripping test.
788-788: LGTM!Index adjusted from
[0][5]to[0][4]to correctly accessbranchFilterinupdateSubscriptioncalls afterspaceIdremoval.src/services/github-oauth-service.ts (3)
28-35: LGTM!The
OAuthCallbackResultinterface correctly typesspaceIdasstring | nullwith a clear inline comment explaining the DM channel use case.
105-116: LGTM!The
getAuthorizationUrlmethod signature and JSDoc are updated consistently to accept nullablespaceIdfor DM channel support.
122-131: LGTM!The database insert correctly handles nullable
spaceIdwith a clarifying comment.src/handlers/github-subscription-handler.ts (3)
130-130: LGTM!Correctly updated to use only
channelIdforgetChannelSubscriptionssincechannelIdis globally unique in Towns.
328-329: LGTM!Correctly updated to use only
channelIdforgetChannelSubscriptions.
537-540: LGTM!
handleStatuscorrectly simplified to only destructurechannelIdsincespaceIdis no longer needed for subscription queries.AGENTS.md (2)
17-55: Well-documented discriminated union pattern.The discriminated union based on
isDmis clearly documented with proper type narrowing examples. This accurately reflects how TypeScript will narrowspaceIdtonullorstringbased on the discriminator.
361-362: LGTM on Hono app guidance.Clear documentation that
bot.start()returns a Hono app with a reference to Hono's best practices for extending routes.src/routes/oauth-callback.ts (3)
64-110: Logic correctly simplified for DM support.The condition change from requiring
spaceIdto only checkingredirectData.repo && townsUserIdaligns with the PR objective. The comment on line 74 appropriately documents thatspaceIdmay be null for DMs.
114-145: Consistent with subscribe branch changes.The subscribe-update flow correctly mirrors the pattern from the subscribe branch, removing spaceId from the precondition check.
148-176: Unsubscribe-update flow correctly updated.The unsubscribe-update branch properly removes spaceId dependency while ensuring required parameters (
repo,townsUserId,eventTypes) are validated.src/utils/oauth-helpers.ts (3)
18-39: Type signature correctly widened.The
spaceId: string | nullchange allows this function to support DM channels where spaceId is null.
116-183: Token status handler correctly updated.The
handleInvalidOAuthTokenfunction properly passes the nullablespaceIdto bothsendEditableOAuthPrompt(for NotLinked and Invalid cases) andoauthService.getAuthorizationUrl(for Unknown case). The exhaustive switch pattern is correctly maintained.
59-111: Core OAuth prompt function correctly updated.The type change propagates correctly through to
oauthService.getAuthorizationUrl. The error logging will shownullfor DM channels, which provides useful debugging context.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
Since channelId is globally unique in Towns, spaceId was redundant for subscription queries. This change: - Makes spaceId nullable in database schema (oauth_states, github_subscriptions, pending_subscriptions) - Updates unique indexes to use only channelId + repoFullName - Removes spaceId from function signatures and WHERE clauses - Simplifies service calls throughout the codebase DMs now work because spaceId can be null without breaking queries. Space subscriptions continue to store spaceId for reference. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
d8944ca to
04f42ff
Compare
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.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
AGENTS.md (2)
297-301: Critical: Example usesspaceIdwithout null check.The ban command example passes
e.spaceIdtohasAdminPermissionandbanwithout verifying the event is not a DM. With the new DM support,spaceIdwill benullin DM contexts, which could cause runtime errors or unexpected behavior.🔒 Proposed fix with DM check
// Pattern bot.onSlashCommand("ban", async (h, e) => { + if (e.isDm) { + return await h.sendMessage(e.channelId, "Ban command only works in spaces"); + } if (!(await h.hasAdminPermission(e.userId, e.spaceId))) return await h.sendMessage(e.channelId, "No perm"); await h.ban(e.mentions[0].userId, e.spaceId); });
253-253: ClarifyspaceIdrequirements for space-only operations.Line 253 shows
createChannel(spaceId, ...)and Line 364 mentions needingspaceIdfor external interactions. With DM support, clarify that:
- Some operations (like
createChannel,ban,hasAdminPermission) require a non-nullspaceIdand only work in space contexts- Developers should check
isDmbefore calling space-only operationsConsider adding a note about which handler methods require
spaceIdvs work in both DM and space contexts.Also applies to: 364-364
🤖 Fix all issues with AI agents
In `@AGENTS.md`:
- Line 361: Wrap the bare URL in the sentence mentioning `bot.start()` in
AGENTS.md with angle brackets so it becomes
<https://hono.dev/docs/guides/best-practices#building-a-larger-application>,
ensuring markdown linting passes; update the line that reads "`bot.start()`
returns a **Hono app**. To extend with additional routes, create a new Hono app
and use `.route('/', app)` per
https://hono.dev/docs/guides/best-practices#building-a-larger-application" to
include the angle-bracketed URL.
♻️ Duplicate comments (1)
CONTRIBUTING.md (1)
63-70: Malformed Markdown table remains unfixed.This table still has the issues previously identified:
- The header separator row (line 64) has an extra column separator creating 4 columns
- The pipe character in line 66's shell command (
base64 -i key.pem | tr -d '\n') is unescaped and will be interpreted as a column separatorThese issues will prevent the table from rendering correctly.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
AGENTS.mdCONTRIBUTING.mdREADME.mddrizzle/0009_nice_eddie_brock.sqldrizzle/meta/0009_snapshot.jsondrizzle/meta/_journal.jsonpackage.jsonsrc/db/schema.tssrc/handlers/github-subscription-handler.tssrc/routes/oauth-callback.tssrc/services/github-oauth-service.tssrc/services/message-delivery-service.tssrc/services/subscription-service.tssrc/utils/oauth-helpers.tstests/unit/handlers/github-subscription-handler.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- src/utils/oauth-helpers.ts
- drizzle/meta/_journal.json
- src/handlers/github-subscription-handler.ts
- src/routes/oauth-callback.ts
- package.json
- tests/unit/handlers/github-subscription-handler.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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@usernameformat
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.tssrc/services/github-oauth-service.tssrc/db/schema.tssrc/services/subscription-service.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.tssrc/services/github-oauth-service.tssrc/db/schema.tssrc/services/subscription-service.ts
AGENTS.md
📄 CodeRabbit inference engine (CLAUDE.md)
Document agent capabilities, responsibilities, and interfaces in AGENTS.md
Files:
AGENTS.md
🧠 Learnings (8)
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: All event handlers receive a base payload including userId, spaceId, channelId, eventId, and createdAt - use eventId as threadId/replyId when responding to maintain event threading
Applied to files:
src/services/message-delivery-service.tssrc/db/schema.tsAGENTS.md
📚 Learning: 2025-11-18T23:35:49.436Z
Learnt from: shuhuiluo
Repo: HereNotThere/bot-github PR: 26
File: src/db/index.ts:61-69
Timestamp: 2025-11-18T23:35:49.436Z
Learning: In `webhook_deliveries` table (src/db/index.ts), the `installation_id` column should NOT have a FOREIGN KEY constraint because the table serves as an immutable audit log for idempotency tracking. Records must persist independently even after installations are deleted, and a foreign key would create race conditions when webhooks arrive before installation records are created. The field is intentionally nullable to support webhooks without installation context.
Applied to files:
src/db/schema.tsdrizzle/meta/0009_snapshot.json
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Use bot.* handler methods directly (outside event handlers) for unprompted messages via webhooks, timers, or tasks - requires channelId, spaceId, or other context stored externally
Applied to files:
AGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Implement event handlers for onMessage, onSlashCommand, onReaction, onTip, and onInteractionResponse to respond to Towns Protocol events
Applied to files:
AGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : User IDs are hex addresses in format 0x..., not usernames - use them consistently throughout event handling and message sending
Applied to files:
AGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Slash commands do not trigger onMessage - register slash command handlers using bot.onSlashCommand() instead
Applied to files:
AGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Define slash commands in src/commands.ts as a const array with name and description properties, then register handlers using bot.onSlashCommand()
Applied to files:
AGENTS.md
📚 Learning: 2025-11-25T03:24:12.463Z
Learnt from: CR
Repo: HereNotThere/bot-github PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:24:12.463Z
Learning: Applies to **/*.ts : Include required environment variables: APP_PRIVATE_DATA (bot credentials) and JWT_SECRET (webhook security token)
Applied to files:
CONTRIBUTING.md
🧬 Code graph analysis (1)
src/services/subscription-service.ts (1)
src/db/schema.ts (1)
githubSubscriptions(71-110)
🪛 markdownlint-cli2 (0.18.1)
AGENTS.md
361-361: Bare URL used
(MD034, no-bare-urls)
🔇 Additional comments (30)
CONTRIBUTING.md (1)
27-28: LGTM!The documentation update correctly reflects the new Bun CLI commands for database management. The commands are clear and the comments accurately describe their behavior.
README.md (1)
164-176: LGTM! Improved developer experience with simplified database commands.The updated instructions are clearer and provide a better developer experience. Using
bun db:upandbun db:downabstracts away Docker complexity while the note about named volume persistence helpfully explains data lifecycle during local development.drizzle/meta/0009_snapshot.json (1)
1-911: LGTM - Auto-generated Drizzle snapshot correctly reflects schema changes.The snapshot properly captures:
- Nullable
space_idcolumns across oauth_states, github_subscriptions, pending_subscriptions, and message_mappings- Updated composite primary key for message_mappings excluding space_id
- Recreated unique indexes using only (channel_id, repo_full_name)
src/services/message-delivery-service.ts (5)
37-44: LGTM - DeliveryParams correctly supports nullable spaceId for DM channels.The type change from
stringtostring | nullproperly supports DM channels where spaceId is not available.
199-205: LGTM - handleEdit signature updated for nullable spaceId.
274-281: LGTM - handleCreate signature updated for nullable spaceId.
333-372: LGTM - storeMapping correctly handles nullable spaceId with proper conflict resolution.The
onConflictDoUpdatetarget correctly matches the new composite primary key (channelId, repoFullName, githubEntityType, githubEntityId), and spaceId is properly included in thesetclause to update it on conflict since it's no longer part of the uniqueness constraint.
310-331: LGTM - getMessageId correctly queries without spaceId.The lookup now uses only the globally unique channelId combined with repoFullName and entity identifiers, which aligns with the schema changes where spaceId is removed from the primary key.
src/services/github-oauth-service.ts (3)
28-35: LGTM - OAuthCallbackResult uses undefined for optional spaceId.Using
undefinedin the API layer while the DB usesnullis a sensible pattern that aligns with TypeScript conventions for optional properties.
110-140: LGTM - getAuthorizationUrl correctly handles undefined-to-null conversion.The
spaceId ?? nullconversion on line 126 properly translates the TypeScriptundefinedto SQLnullfor database storage.
236-244: LGTM - handleCallback correctly converts null back to undefined.The
stateData.spaceId ?? undefinedon line 240 properly converts the databasenullback to TypeScriptundefinedfor the API response, maintaining consistency with theOAuthCallbackResultinterface.src/db/schema.ts (4)
53-53: LGTM - oauthStates.spaceId made nullable with clear documentation.
74-110: LGTM - githubSubscriptions schema correctly updated.The nullable spaceId and updated unique index on
(channelId, repoFullName)properly support DM channels. The inline comment on line 102 clearly explains the rationale.
203-231: LGTM - pendingSubscriptions schema correctly updated.Consistent with githubSubscriptions: nullable spaceId and unique index on
(channelId, repoFullName)with explanatory comment.
237-279: LGTM - messageMappings schema correctly updated with new primary key.The composite primary key now correctly excludes spaceId, using only
(channelId, repoFullName, githubEntityType, githubEntityId). This aligns with the service layer changes inmessage-delivery-service.tswhereonConflictDoUpdatetargets these same columns.drizzle/0009_nice_eddie_brock.sql (1)
1-10: Verify no duplicate(channel_id, repo_full_name)pairs exist across all spaces before running this migration.The migration order is correct—it drops indexes before altering columns, drops the old PK, makes columns nullable, then adds the new constraint. However, the migration fundamentally changes the data model from per-space uniqueness to global uniqueness.
Original constraint:
(space_id, channel_id, repo_full_name)was unique within each space.
New constraint:(channel_id, repo_full_name)is now globally unique across all spaces.If existing data contains duplicate
(channel_id, repo_full_name)pairs in different spaces, the migration will fail on line 8 when creating the new PK. Before running in production, execute these checks:Pre-migration verification queries
-- Check github_subscriptions for duplicates across spaces SELECT channel_id, repo_full_name, COUNT(*) as count, COUNT(DISTINCT space_id) as space_count FROM github_subscriptions GROUP BY channel_id, repo_full_name HAVING COUNT(*) > 1; -- Check pending_subscriptions for duplicates across spaces SELECT channel_id, repo_full_name, COUNT(*) as count, COUNT(DISTINCT space_id) as space_count FROM pending_subscriptions GROUP BY channel_id, repo_full_name HAVING COUNT(*) > 1; -- Check message_mappings for duplicates on new PK fields SELECT channel_id, repo_full_name, github_entity_type, github_entity_id, COUNT(*) as count, COUNT(DISTINCT space_id) as space_count FROM message_mappings GROUP BY channel_id, repo_full_name, github_entity_type, github_entity_id HAVING COUNT(*) > 1;If any rows are returned, those duplicates must be resolved before the migration can proceed.
src/services/subscription-service.ts (11)
44-51: LGTM!The
SubscribeParamsinterface correctly makesspaceIdoptional with a clear comment explaining that DM channels will have undefined. This aligns with the channelId-centric model where channelId is globally unique.
249-265: LGTM!The subscription creation correctly:
- Converts
spaceIdfromundefined(API convention) tonull(DB convention) usingspaceId ?? null- Uses
channelId + repoFullNamefor the uniqueness check, matching the database unique index
321-335: LGTM!The update query correctly uses only
channelIdandrepoFullNamein the WHERE clause, consistent with the channelId-centric uniqueness model.
383-411: LGTM!The
removeEventTypesmethod correctly updates theunsubscribecall to use the new signature and maintains consistency in the WHERE clause logic.
462-468: LGTM!The conversion
sub.spaceId ?? undefinedcorrectly handles the DB-to-API convention, convertingnull(stored in DB) back toundefined(expected bySubscribeParams.spaceId).
503-516: LGTM!The
unsubscribemethod signature correctly removesspaceId, aschannelIdalone provides sufficient uniqueness for subscription identification.
521-555: LGTM!The
getSubscriptionmethod correctly simplifies to use onlychannelIdandrepoFullNameas parameters, aligning with the globally unique channelId model.
560-585: LGTM!The
getChannelSubscriptionsmethod correctly simplifies to use onlychannelIdfor filtering, which is sufficient given the globally unique channel IDs.
595-628: LGTM!The
getRepoSubscribersmethod correctly returnsspaceId: string | nullto indicate that DM channels will havenullfor spaceId. This allows consumers to handle DM vs space channel delivery appropriately.
960-988: LGTM!The
storePendingSubscriptionmethod correctly handles optionalspaceIdwith the sameparams.spaceId ?? nullpattern used elsewhere for DB storage.
1018-1023: LGTM!The
getSubscriptioncall correctly uses the updated signature with onlychannelIdandrepoFullName.AGENTS.md (3)
17-39: LGTM! Well-structured discriminated union.The discriminated union pattern correctly models DM vs Space channels using the
isDmdiscriminator. The type definition enables proper TypeScript narrowing and clearly documents the relationship betweenisDmandspaceIdnullability.
41-54: Excellent type narrowing documentation.The example clearly demonstrates how TypeScript narrows the
BasePayloadtype based on theisDmdiscriminator. This will help developers avoid runtime null errors when accessingspaceId.
113-119: Clarify the status of paid commands documentation.The
paid: { price: '$0.20' }syntax shown in the paid commands example (lines 113-119) is not present anywhere in the actual codebase. The command definitions insrc/commands.tscurrently only supportnameanddescriptionproperties. This documentation appears to describe a feature that hasn't been implemented yet. Either:
- Mark this section as planned/aspirational functionality
- Remove it until the feature is available
- Add implementation examples if this is an actual supported feature
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
Since channelId is globally unique in Towns, spaceId was redundant for subscription queries. This change:
DMs now work because spaceId can be null without breaking queries. Space subscriptions continue to store spaceId for reference.
Summary by CodeRabbit
Release Notes
New Features
Improvements
bun db:up/bun db:down)Documentation
✏️ Tip: You can customize this high-level summary in your review settings.