feat(github): terminate Devin sessions when PR is closed/merged#2014
Conversation
- Add PR closed webhook handler that finds and terminates linked Devin sessions - Implement smart pagination for session lookup based on PR creation date - Add tests for the webhook handler with mocked Devin API responses Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
📝 WalkthroughWalkthroughAdds a new Probot app in Changes
Sequence Diagram(s)sequenceDiagram
participant GitHub as GitHub (Webhook)
participant Probot
participant DevinAPI as Devin API
GitHub->>Probot: pull_request.closed event
activate Probot
Probot->>Probot: Read DEVIN_API_KEY
alt No API Key
Probot->>Probot: Log & exit
else API Key Present
loop paginated search
Probot->>DevinAPI: GET /sessions?limit&offset
DevinAPI-->>Probot: ListSessionsResponse (sessions[])
end
alt Session Found
Probot->>Probot: Ensure session.state == "running"
alt running
Probot->>DevinAPI: DELETE /sessions/{id}
DevinAPI-->>Probot: 200 OK
Probot->>Probot: Log termination
else not running
Probot->>Probot: Log & exit
end
else No Matching Session
Probot->>Probot: Log & exit
end
end
deactivate Probot
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
✅ Deploy Preview for hyprnote ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for hyprnote-storybook ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/github/test/index.test.ts (1)
24-96: Consider adding test coverage for non-running session status.The test suite covers the main scenarios well. However, consider adding a test case for when a Devin session is found but its status is not "running" (e.g., "completed" or "paused"). According to the implementation (src/index.ts lines 130-134), termination should be skipped in this case.
Example test case to add:
test("skips termination when session is not running", async () => { process.env.DEVIN_API_KEY = "test-api-key"; nock("https://api.devin.ai") .get("/v1/sessions") .query({ limit: "100", offset: "0" }) .reply(200, { sessions: [ { session_id: "devin-456", status: "completed", title: "Test PR", created_at: "2024-01-01T00:00:00Z", updated_at: "2024-01-01T00:01:00Z", snapshot_id: null, playbook_id: null, pull_request: { url: "https://github.com/example/repo/pull/123", }, }, ], }); // No DELETE should be called await probot.receive({ name: "pull_request", payload }); expect(nock.isDone()).toBe(true); });
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
apps/github/package.json(1 hunks)apps/github/src/index.ts(1 hunks)apps/github/test/fixtures/pull_request.closed.json(1 hunks)apps/github/test/index.test.ts(1 hunks)apps/github/tsconfig.json(1 hunks)apps/github/vitest.config.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces
Files:
apps/github/test/index.test.tsapps/github/vitest.config.tsapps/github/src/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/github/test/index.test.tsapps/github/vitest.config.tsapps/github/src/index.ts
**/*.config.{ts,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Agent configuration should be centralized and externalized from implementation logic
Files:
apps/github/vitest.config.ts
🧠 Learnings (4)
📚 Learning: 2025-11-24T16:32:23.055Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: After some amount of TypeScript changes, run `pnpm -r typecheck`.
Applied to files:
apps/github/tsconfig.json
📚 Learning: 2025-11-24T16:32:19.706Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx} : Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props. Just inline them.
Applied to files:
apps/github/tsconfig.json
📚 Learning: 2025-11-24T16:32:23.055Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: Applies to **/*.{ts,tsx} : Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Applied to files:
apps/github/tsconfig.json
📚 Learning: 2025-11-24T16:32:01.459Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:32:01.459Z
Learning: Applies to **/*.ts : Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Applied to files:
apps/github/tsconfig.json
🧬 Code graph analysis (1)
apps/github/test/index.test.ts (1)
apps/web/scripts/gen-agents.js (1)
__dirname(7-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Redirect rules - hyprnote-storybook
- GitHub Check: Header rules - hyprnote-storybook
- GitHub Check: Pages changed - hyprnote-storybook
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: fmt
🔇 Additional comments (9)
apps/github/tsconfig.json (1)
1-25: LGTM! Well-configured TypeScript setup.The configuration appropriately targets ES2022 with Node16 module resolution for ESM, enables strict type-checking, and includes useful additional checks. The output structure (lib/) aligns with the package.json start script.
apps/github/src/index.ts (5)
3-18: LGTM! Appropriate inline type definitions.The interfaces correctly model the Devin API response structure and follow the coding guideline to inline types when they're not shared across files.
20-43: LGTM! Clean API wrapper with appropriate error handling.The function correctly constructs the request with pagination parameters and authorization. The type assertion at line 42 assumes the API returns valid data; runtime validation could improve robustness but is not critical for this integration.
45-64: LGTM!The termination logic is straightforward and handles errors appropriately.
100-107: LGTM!Simple health check endpoint for monitoring the app.
109-149: LGTM! Well-structured PR closed handler with proper error handling.The handler correctly:
- Gracefully handles missing API key
- Searches for the associated Devin session
- Only terminates sessions in "running" status
- Logs all actions and errors appropriately
apps/github/test/fixtures/pull_request.closed.json (1)
1-21: LGTM!The fixture correctly models a closed/merged pull request event with all necessary fields for testing the PR closed handler.
apps/github/vitest.config.ts (1)
1-10: LGTM!Standard Vitest configuration with v8 coverage provider and appropriate test file pattern.
apps/github/package.json (1)
19-19: Update TypeScript to the latest stable version.TypeScript 5.8.3 is valid but outdated; TypeScript 5.9 was released on July 22, 2025 and is the current stable version. Consider updating to 5.9 to benefit from latest improvements and bug fixes.
⛔ Skipped due to learnings
Learnt from: CR Repo: fastrepl/hyprnote PR: 0 File: AGENTS.md:0-0 Timestamp: 2025-11-24T16:32:23.055Z Learning: After some amount of TypeScript changes, run `pnpm -r typecheck`.Learnt from: CR Repo: fastrepl/hyprnote PR: 0 File: AGENTS.md:0-0 Timestamp: 2025-11-24T16:32:19.706Z Learning: After some amount of TypeScript changes, run `pnpm -r typecheck`.Learnt from: CR Repo: fastrepl/hyprnote PR: 0 File: CLAUDE.md:0-0 Timestamp: 2025-11-24T16:32:01.459Z Learning: Applies to **/*.ts : Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
…ssions - Remove prCreatedAt parameter and early-exit logic that assumed descending order - Add per-page sorting by created_at descending as suggested in review - Continue paginating until match found or all pages exhausted - Add tests for multi-page pagination and out-of-order sessions Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/github/src/index.ts (2)
20-64: HTTP helpers look correct; consider DRYing base URL and enriching error contextBoth list and terminate helpers are straightforward and handle non-OK responses properly. As a small nicety, you could extract
https://api.devin.ai/v1into a shared constant and, on failure, optionally log or include a snippet of the response body to aid debugging, but the current implementation is functionally fine.
66-99: Pagination + per-page sorting logic is robust; limits could be configurableIterating up to
maxIterationswithlimit = 100(up to 5,000 sessions) and sorting each page bycreated_atbefore preferring the newest matching session avoids relying on backend ordering and should work well for typical usage. If you expect very large accounts, consider makinglimitand/ormaxIterationsconfigurable (env or app config) so you can tune scan depth without code changes.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/github/src/index.ts(1 hunks)apps/github/test/index.test.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/github/test/index.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces
Files:
apps/github/src/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/github/src/index.ts
🧬 Code graph analysis (1)
apps/github/src/index.ts (2)
packages/db/src/schema.ts (1)
sessions(121-134)apps/web/src/router.tsx (1)
getRouter(8-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: fmt
🔇 Additional comments (1)
apps/github/src/index.ts (1)
3-18: Session type definitions are clear and scoped appropriately
DevinSessionandListSessionsResponsecapture only the fields you actually use and keep the API-specific shape local to this module, which is a good balance between type safety and over-modeling. No changes needed here.
Summary
Adds a new Probot app in
apps/githubthat automatically terminates linked Devin sessions when a PR is closed or merged. When apull_request.closedwebhook is received, the app searches for a Devin session associated with that PR URL and terminates it if found and still running.The session lookup paginates through all sessions (up to 5000) until a match is found or all pages are exhausted. Sessions are sorted by
created_atdescending on each page, but correctness does not depend on any particular API ordering.Updates since last revision
created_atordercreated_atdescending as suggested in reviewReview & Testing Checklist for Human
Verify PR URL format matching: The code compares
session.pull_request.urlfrom Devin API withpr.html_urlfrom GitHub webhook. Confirm these use the same format (e.g.,https://github.com/owner/repo/pull/123). If they differ, no sessions will ever match.Deployment configuration: The app has a Dockerfile but no
fly.toml,app.yml, or other deployment config. This needs to be added before the app can actually run.Test with real Devin sessions: Run the app locally with a real
DEVIN_API_KEYand close a PR that has a linked Devin session to verify end-to-end behavior.Notes
DEVIN_API_KEYis not set"running"are terminatedRequested by: @yujonglee (yujonglee.dev@gmail.com)
Link to Devin run: https://app.devin.ai/sessions/d8fb87cc16a74bd3be780736486e86fa