Skip to main content

Auto-Fix Failing CI Builds

Add a GitHub Action that calls Devin to fix failing CI on your PRs.
AuthorCognition
CategoryAutomations
FeaturesAPI
1

Store your Devin API key in GitHub

The workflow calls Devin’s v3 API to create sessions programmatically. Create a service user and store its token as a GitHub Actions secret:
  1. Go to app.devin.ai > Settings > Service Users and create a service user with ManageOrgSessions permission
  2. Copy the API token shown after creation — it’s only displayed once
  3. In your GitHub repo, navigate to Settings > Secrets and variables > Actions
  4. Add two secrets: DEVIN_API_KEY (the token) and DEVIN_ORG_ID (your organization ID — get it by calling GET https://api.devin.ai/v3/enterprise/organizations with your token)
Make sure the repository is already set up on Devin’s Machine so Devin can clone, build, and push to it.
2

Add the workflow file

Create .github/workflows/devin-ci-fix.yml. This workflow fires whenever your existing CI workflow completes with a failure, extracts the failing job names, and calls the Devin API to start a fix session:
name: Auto-fix CI with Devin

on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]

jobs:
  trigger-devin-fix:
    if: >
      github.event.workflow_run.conclusion == 'failure' &&
      github.event.workflow_run.pull_requests[0]
    runs-on: ubuntu-latest
    steps:
      - name: Get failure details
        id: failure
        uses: actions/github-script@v7
        with:
          script: |
            const run = context.payload.workflow_run;
            const pr = run.pull_requests[0];
            const jobs = await github.rest.actions.listJobsForWorkflowRun({
              owner: context.repo.owner,
              repo: context.repo.repo,
              run_id: run.id
            });
            const failed = jobs.data.jobs
              .filter(j => j.conclusion === 'failure')
              .map(j => j.name);
            core.setOutput('pr_number', pr.number);
            core.setOutput('branch', pr.head.ref);
            core.setOutput('failed_jobs', failed.join(', '));
            core.setOutput('run_url', run.html_url);

      - name: Trigger Devin session
        run: |
          curl -s -X POST "https://api.devin.ai/v3/organizations/${{ secrets.DEVIN_ORG_ID }}/sessions" \
            -H "Authorization: Bearer ${{ secrets.DEVIN_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d "{
              \"prompt\": \"CI failed on PR #${{ steps.failure.outputs.pr_number }} in ${{ github.repository }}. Failed jobs: ${{ steps.failure.outputs.failed_jobs }}. Run: ${{ steps.failure.outputs.run_url }}. Branch: ${{ steps.failure.outputs.branch }}. Read the CI logs, identify the root cause, and push a fix to the branch.\"
            }"
Replace "CI" in the workflows array with the exact name: from your existing CI workflow file (e.g., "Tests", "Build & Test").Use the tags field in the request body (e.g., "tags": ["ci-fix", "pr-312"]) to track which CI failures have already triggered sessions and avoid duplicates.
3

What happens when CI fails

When a PR’s CI run fails, the Action extracts failure details and passes them to Devin as a session prompt. Here’s a typical auto-fix flow:
  1. Reads the CI logs — Devin opens the run URL and parses the error output, stack traces, and test results from the failing jobs
  2. Traces the error to code — Locates the relevant file and line on the PR branch (e.g., UserList.tsx:34) and reads the surrounding code and recent diff
  3. Pushes a fix — Commits a targeted change directly to the PR branch, which re-triggers CI automatically
  4. Comments on the PR — Posts a summary explaining the root cause and what was changed
Example PR comment from Devin:
CI failure in test-unit — fixed

Root cause: `UserList.tsx:34` calls `.map()` on `props.users`, which is
undefined when the API returns an empty response body instead of `[]`.

Fix: Added a fallback — `const users = props.users ?? [];`
Added a test case for the empty-response scenario.
All 312 tests passing.
4

Scope it to the right failures

Not every CI failure benefits from an auto-fix — infrastructure timeouts and Docker build issues won’t be solved by a code change. Add a condition so only relevant job failures trigger Devin:
      - name: Trigger Devin session
        if: >
          contains(steps.failure.outputs.failed_jobs, 'test') ||
          contains(steps.failure.outputs.failed_jobs, 'lint') ||
          contains(steps.failure.outputs.failed_jobs, 'typecheck')
        run: |
          curl -s -X POST "https://api.devin.ai/v3/organizations/${{ secrets.DEVIN_ORG_ID }}/sessions" \
          ...

Keep fixes reviewable

Devin pushes a fix commit, but the PR still requires human review before merging. Treat auto-fixes as a head start for the developer, not a replacement for code review. If Devin can’t resolve the failure, it comments on the PR explaining what it found so an engineer can pick up from there.