Skip to content

feat(github): terminate Devin sessions when PR is closed/merged#2014

Merged
yujonglee merged 2 commits into
mainfrom
devin/1764499904-github-pr-session-termination
Nov 30, 2025
Merged

feat(github): terminate Devin sessions when PR is closed/merged#2014
yujonglee merged 2 commits into
mainfrom
devin/1764499904-github-pr-session-termination

Conversation

@yujonglee
Copy link
Copy Markdown
Contributor

@yujonglee yujonglee commented Nov 30, 2025

Summary

Adds a new Probot app in apps/github that automatically terminates linked Devin sessions when a PR is closed or merged. When a pull_request.closed webhook 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_at descending on each page, but correctness does not depend on any particular API ordering.

Updates since last revision

  • Removed early-exit logic that incorrectly assumed sessions were returned in descending created_at order
  • Now paginates through all sessions without date-based assumptions
  • Added per-page sorting by created_at descending as suggested in review
  • Added tests for multi-page pagination and out-of-order session scenarios

Review & Testing Checklist for Human

  • Verify PR URL format matching: The code compares session.pull_request.url from Devin API with pr.html_url from 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_KEY and close a PR that has a linked Devin session to verify end-to-end behavior.

Notes

  • The app gracefully skips termination if DEVIN_API_KEY is not set
  • Only sessions with status "running" are terminated
  • Tests use nock to mock Devin API responses (5 test cases covering basic flow, pagination, and out-of-order scenarios)
  • Pagination is bounded to 50 iterations × 100 sessions = 5000 max sessions searched

Requested by: @yujonglee (yujonglee.dev@gmail.com)
Link to Devin run: https://app.devin.ai/sessions/d8fb87cc16a74bd3be780736486e86fa

- 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-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 30, 2025

📝 Walkthrough

Walkthrough

Adds a new Probot app in apps/github that listens for pull_request.closed webhooks, searches Devin sessions via a paginated Devin API, and terminates a matching running session. Includes package manifest, TypeScript/Vitest configs, and tests.

Changes

Cohort / File(s) Summary
Package Configuration
apps/github/package.json
New Node package manifest for @hypr/github: metadata, scripts (build/typecheck/start/test), dependencies (probot), devDependencies (TypeScript, Vitest, nock, smee-client, @types/node), Node >=18, ESM.
App Source
apps/github/src/index.ts
New Probot app default export: GET /health route; PR closed handler that reads DEVIN_API_KEY, paginates listDevinSessions, finds a session matching PR URL or created before PR, and calls terminateDevinSession for running sessions; includes typed helpers and logging/error handling.
Tests & Fixtures
apps/github/test/index.test.ts, apps/github/test/fixtures/pull_request.closed.json
New Vitest test suite and PR-closed fixture covering: no API key, no matching session, successful termination, pagination, and unordered results; uses nock to mock Devin API and cleans env/mocks.
Tooling Configuration
apps/github/tsconfig.json, apps/github/vitest.config.ts
New TypeScript config with strict checks, ES2022 target, Node16 resolution, output ./lib; Vitest config includes test/**/*.test.ts and v8 coverage provider.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect pagination/search cutoff (limit/offset and created_at comparison) in apps/github/src/index.ts
  • Verify HTTP error handling and logging paths, and that non-OK responses raise appropriate errors
  • Review tests (apps/github/test/index.test.ts) for correct nock scopes and environment cleanup

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly summarizes the main change: adding functionality to terminate Devin sessions when a PR is closed or merged, which is the core purpose of this PR.
Description check ✅ Passed The description is detailed and directly related to the changeset, explaining the new Probot app, session termination logic, pagination approach, and includes testing details.
✨ 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 devin/1764499904-github-pr-session-termination

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

@netlify
Copy link
Copy Markdown

netlify Bot commented Nov 30, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 58900d9
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/692c250063987c00085f5566
😎 Deploy Preview https://deploy-preview-2014--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented Nov 30, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 58900d9
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/692c250038ccc40008122379
😎 Deploy Preview https://deploy-preview-2014--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown
Contributor

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between ccb2a27 and e50f8eb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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.ts
  • apps/github/vitest.config.ts
  • 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, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/github/test/index.test.ts
  • apps/github/vitest.config.ts
  • apps/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

Comment thread apps/github/src/index.ts
…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>
Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (2)
apps/github/src/index.ts (2)

20-64: HTTP helpers look correct; consider DRYing base URL and enriching error context

Both list and terminate helpers are straightforward and handle non-OK responses properly. As a small nicety, you could extract https://api.devin.ai/v1 into 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 configurable

Iterating up to maxIterations with limit = 100 (up to 5,000 sessions) and sorting each page by created_at before preferring the newest matching session avoids relying on backend ordering and should work well for typical usage. If you expect very large accounts, consider making limit and/or maxIterations configurable (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

📥 Commits

Reviewing files that changed from the base of the PR and between e50f8eb and 58900d9.

📒 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, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-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

DevinSession and ListSessionsResponse capture 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.

Comment thread apps/github/src/index.ts
@yujonglee yujonglee merged commit b94ee38 into main Nov 30, 2025
16 checks passed
@yujonglee yujonglee deleted the devin/1764499904-github-pr-session-termination branch November 30, 2025 11:13
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.

1 participant