Your GitHub Actions runs are getting cancelled because the system decided they’d gone on too long or too many were running at once.
Common Causes and Fixes
1. Concurrency Limit Reached
-
Diagnosis: Check the "Concurrency" section of your workflow run. You’ll see a message like "This run was cancelled because a new run triggered for the same job or workflow on a different ref." This indicates you’ve hit the concurrency limit for that specific workflow or job.
-
Cause: GitHub Actions has a default concurrency limit to prevent runaway jobs and excessive resource usage. If you trigger multiple runs of the same workflow rapidly (e.g., through frequent commits to the same branch, or multiple branches being pushed), you can exceed this limit.
-
Fix: Increase the concurrency limit for your workflow. In your workflow
.ymlfile, add or modify theconcurrencyblock.name: CI on: [push] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true # This is the default, but explicitly setting it is good practice.To increase the limit, you’d typically adjust the
groupor use a different identifier if you want more concurrent runs for the same group. However, the more common scenario is that you want to prevent cancellation due to concurrency. Thecancel-in-progress: truesetting is what cancels the older run when a new one starts for the same group. If you want to allow multiple runs for the same group, you’d need to remove thecancel-in-progress: trueor adjust thegroupidentifier so that subsequent runs don’t fall into the same group.A more common and often correct approach is to ensure your
groupis specific enough to avoid unintended cancellations. For instance, if you want to limit concurrency per branch, the defaultgroup: ${{ github.workflow }}-${{ github.ref }}is usually correct. If you want to limit concurrency globally for a workflow, you’d usegroup: ${{ github.workflow }}.The actual fix for preventing cancellation due to concurrency is often to ensure your concurrency group is well-defined and that you don’t have more jobs starting than your desired limit allows. If you see cancellations, it means the newer job is cancelling the older one because
cancel-in-progressis true. To allow all jobs to run (even if it means many at once), you’d either:- Remove
cancel-in-progress: trueif you’re okay with potentially many jobs running simultaneously and not cancelling each other. - Or, more practically, ensure your
groupidentifier is more granular if you want to allow multiple runs within a broader scope but limit them within a tighter scope (e.g., allow multiple pushes to different branches to run, but only one run per specific branch).
If you are seeing cancellations and you want the newer run to not cancel the older one, then you need to ensure that the
groupidentifier is different for the runs you want to run concurrently. For example, if you’re pushing tomainmultiple times very quickly, and each push triggers a workflow, they will all share the samegroup: "CI-main"and the newer ones will cancel the older ones. If you don’t want them to cancel, you’d need a more dynamic group identifier, which is rarely the desired behavior. The typical reason for cancellation is hitting the limit, and the fix is to understand why you’re hitting it and adjust your workflow triggers or concurrency group. - Remove
-
Why it works: The
concurrencysetting allows you to define a group of jobs that should run exclusively. When a new job starts for a group wherecancel-in-progressis true and the group is already running jobs, GitHub Actions cancels the existing job(s) in that group to make way for the new one. By carefully defining thegroupand understandingcancel-in-progress, you control this behavior.
2. Workflow Timeout Reached
-
Diagnosis: Look at the workflow run summary. You’ll see a status indicating "Cancelled" and often a timestamp showing when it was cancelled, suggesting it ran for a long duration. The job logs themselves might also have a message indicating it was cancelled due to exceeding the maximum runtime.
-
Cause: GitHub Actions jobs have a maximum runtime. For self-hosted runners, this is unlimited. For GitHub-hosted runners, it’s 360 minutes (6 hours) for public repositories and 3600 minutes (60 hours) for private repositories. If a job exceeds this, it’s automatically cancelled.
-
Fix: Optimize your workflow to run faster, or if your workflow legitimately needs more time, consider migrating to self-hosted runners. For GitHub-hosted runners, ensure your build/test/deploy steps are efficient. This might involve:
- Parallelizing tests.
- Caching dependencies effectively.
- Reducing the amount of work done per run (e.g., only building changed components).
- For private repos, ensuring you’re not hitting the 60-hour limit. If you are, that’s an extreme case and self-hosted runners are strongly recommended.
jobs: build: runs-on: ubuntu-latest timeout-minutes: 60 # Example: Set a specific timeout for this job (optional, defaults apply) steps: - uses: actions/checkout@v3 # ... your steps ...Note: Setting
timeout-minutesto a higher value than the default (e.g.,timeout-minutes: 360for public,timeout-minutes: 3600for private) does not bypass the system limits. It only allows you to set a lower limit if you wish. -
Why it works: By optimizing your workflow, you reduce the execution time. If the task genuinely requires more time than the GitHub-hosted runner limits, migrating to self-hosted runners removes this constraint entirely.
3. Manual Cancellation
- Diagnosis: Check the "Summary" tab of the workflow run in GitHub. Look for who cancelled the run. If it says "Cancelled by [username]" or "Cancelled by GitHub Actions," it might have been a manual intervention. Also, check the "Actions" tab for any "Cancel workflow run" buttons that might have been clicked accidentally.
- Cause: Someone with write access to the repository can manually cancel a running workflow from the GitHub UI. This can happen accidentally or intentionally if a run is deemed unnecessary or problematic.
- Fix: Be mindful of who has write access and the "Cancel workflow run" button. Train team members on its use. If you’re automating cancellation (e.g., via a webhook or another workflow), ensure that logic is correct. There’s no configuration to prevent manual cancellation by authorized users, as it’s a core feature.
- Why it works: This isn’t a "fix" in the sense of changing system behavior, but rather a procedural one. Understanding that manual cancellation is possible helps prevent accidental triggers.
4. GitHub Platform Issues
- Diagnosis: Check the GitHub Status page (https://www.githubstatus.com/). If there are active incidents related to Actions or runner availability, this could be the cause. Look for messages about "Actions runner degradation" or "workflow cancellations."
- Cause: Occasionally, GitHub’s own infrastructure can experience issues that lead to unexpected workflow cancellations. This is rare but possible.
- Fix: There is no fix you can apply. Wait for GitHub to resolve the issue. Keep an eye on the status page.
- Why it works: This is a workaround. You can’t fix an external system failure, only wait for it to be resolved.
5. Incorrect Workflow Trigger Logic
-
Diagnosis: Review your
.github/workflows/*.ymlfiles. Examine theon:section. Are you accidentally triggering workflows too frequently or in unintended ways? For example, apushtomainmight trigger a workflow, and then a subsequentpull_requesttomainfrom a fork might trigger another, potentially leading to concurrency issues or unexpected cancellations if not managed. -
Cause: Complex or overlapping trigger conditions can lead to multiple workflow runs starting simultaneously, especially if they share the same concurrency group.
-
Fix: Refine your
on:triggers. Be precise about which events should start which workflows. For example, useon: push: branches: [main]instead of a broadon: pushif you only want pushes tomainto trigger a specific workflow. If you have multiple workflows that could run on the same event, ensure their concurrency groups are distinct or that you have a clear strategy for which one should win (e.g., usingcancel-in-progress).on: push: branches: - main - develop pull_request: branches: - mainIn this example, both
pushandpull_requestevents targetingmainwould use the default concurrency groupworkflow_name-main. If you want them to run independently, you’d need to assign different concurrency groups. -
Why it works: By making your triggers more specific, you reduce the likelihood of multiple, unintended workflow runs starting concurrently, thus avoiding concurrency limits and potential cancellations.
The next error you might encounter after resolving these is related to specific job failures within a workflow that is running, such as "Job failed" or "Step X failed."