Files
calendar/.github/actions/devin-session/action.yml
T
Keith WilliamsGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ea3bab57b0 refactor: consolidate Devin session logic into reusable action (#26843)
* fix: reuse existing Devin sessions in stale PR completion workflow

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: consolidate Devin session logic into reusable action

- Create .github/actions/devin-session composite action for session checking
- Update stale-pr-devin-completion.yml to use the new action
- Update cubic-devin-review.yml to use the new action
- Update devin-conflict-resolver.yml to use the new action
- Rename workflows to 'PR Labeled' with descriptive job names

The new action checks for existing Devin sessions by:
1. Looking for session URLs in PR body (for PRs created by Devin)
2. Searching PR comments for known Devin session patterns
3. Verifying session is active (working, blocked, or resumed status)

This eliminates duplicated session checking logic across all three workflows.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* revert: restore original workflow names

Reverted workflow names back to descriptive names:
- 'Stale Community PR Devin Completion' (was 'PR Labeled')
- 'Devin PR Conflict Resolver' (was 'PR Labeled')

Descriptive names are the recommended convention as they appear in
GitHub Actions tab, status checks, and notifications.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: add active status check for PR body sessions

Addresses Cubic AI feedback (confidence 9/10): PR body sessions
now verify the session is active (working, blocked, or resumed)
before reusing, matching the behavior of comment-based session checks.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-14 18:02:25 +00:00

125 lines
4.6 KiB
YAML

name: 'Devin Session Checker'
description: 'Check for existing Devin sessions on a PR'
inputs:
devin-api-key:
description: 'Devin API key'
required: true
github-token:
description: 'GitHub token for API calls'
required: true
pr-number:
description: 'PR number to check for existing sessions'
required: true
outputs:
has-existing-session:
description: 'Whether an existing active session was found (true/false)'
value: ${{ steps.check-session.outputs.has-existing-session }}
session-id:
description: 'ID of the existing session (if found)'
value: ${{ steps.check-session.outputs.session-id }}
session-url:
description: 'URL of the existing session (if found)'
value: ${{ steps.check-session.outputs.session-url }}
runs:
using: 'composite'
steps:
- name: Check for existing Devin session
id: check-session
uses: actions/github-script@v7
env:
DEVIN_API_KEY: ${{ inputs.devin-api-key }}
PR_NUMBER: ${{ inputs.pr-number }}
with:
github-token: ${{ inputs.github-token }}
script: |
const { owner, repo } = context.repo;
const prNumber = parseInt(process.env.PR_NUMBER, 10);
const SESSION_URL_REGEX = /app\.devin\.ai\/sessions\/([a-f0-9-]+)/;
const ACTIVE_STATUSES = ['working', 'blocked', 'resumed'];
async function checkSessionStatus(sessionId) {
const response = await fetch(`https://api.devin.ai/v1/sessions/${sessionId}`, {
headers: {
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
console.log(`Failed to fetch session ${sessionId}: ${response.status}`);
return null;
}
return await response.json();
}
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber
});
const prBody = pr.body || '';
const prSessionMatch = prBody.match(SESSION_URL_REGEX);
if (prSessionMatch) {
const sessionId = prSessionMatch[1];
console.log(`PR was created by Devin session: ${sessionId}`);
const session = await checkSessionStatus(sessionId);
if (session) {
console.log(`PR creator session status: ${session.status_enum}`);
if (ACTIVE_STATUSES.includes(session.status_enum)) {
core.setOutput('has-existing-session', 'true');
core.setOutput('session-id', sessionId);
core.setOutput('session-url', `https://app.devin.ai/sessions/${sessionId}`);
return;
} else {
console.log(`PR creator session ${sessionId} is not active, will check comments`);
}
}
}
const devinSessionPatterns = [
'Devin AI is completing this stale PR',
'Devin AI is addressing Cubic AI',
'Devin AI is resolving merge conflicts'
];
const comments = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber
});
for (const comment of comments.data.reverse()) {
const hasDevinSession = devinSessionPatterns.some(pattern => comment.body?.includes(pattern));
if (hasDevinSession) {
const match = comment.body?.match(SESSION_URL_REGEX);
if (match) {
const sessionId = match[1];
console.log(`Found existing Devin session from comment: ${sessionId}`);
const session = await checkSessionStatus(sessionId);
if (session) {
if (ACTIVE_STATUSES.includes(session.status_enum)) {
console.log(`Session ${sessionId} is active (status: ${session.status_enum})`);
core.setOutput('has-existing-session', 'true');
core.setOutput('session-id', sessionId);
core.setOutput('session-url', `https://app.devin.ai/sessions/${sessionId}`);
return;
} else {
console.log(`Session ${sessionId} is not active (status: ${session.status_enum})`);
}
}
}
}
}
console.log('No existing active Devin session found');
core.setOutput('has-existing-session', 'false');
core.setOutput('session-id', '');
core.setOutput('session-url', '');