This workaround can also be used if for any reason you prefer not to enable the “Auto-cancel redundant builds” functionality at the project-level.
Below is a set of instructions that will allow you to auto-cancel redundant workflows on any branch (including the default branch).
You can either:
Put the commands into a script within your repository and call it at the start of the workflow (either within a separate job or at the start of the first existing job in the workflow)
Note that you’ll need to add a personal API key as an environment variable MyToken .
#!/bin/bash
## Get the name of the workflow and the related pipeline number
curl --header "Circle-Token: $MyToken" --request GET "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}" -o current_workflow.json
WF_NAME=jq -r '.name' current_workflow.json
CURRENT_PIPELINE_NUM=jq -r '.pipeline_number' current_workflow.json
## Get the IDs of pipelines created by the current user on the same branch. (Only consider pipelines that have a pipeline number inferior to the current pipeline)
PIPE_IDS=$(curl --header "Circle-Token: $MyToken" --request GET "https://circleci.com/api/v2/project/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pipeline?branch=$CIRCLE_BRANCH"|jq -r --arg CIRCLE_USERNAME "$CIRCLE_USERNAME" --argjson CURRENT_PIPELINE_NUM "$CURRENT_PIPELINE_NUM" '.items[]|select(.state == "created")|select(.trigger.actor.login == $CIRCLE_USERNAME)|select(.number < $CURRENT_PIPELINE_NUM)|.id')
## Get the IDs of currently running/on_hold workflows that have the same name as the current workflow, in all previously created pipelines.
if [ ! -z "$PIPE_IDS" ]; then
for PIPE_ID in $PIPE_IDS
do
curl --header "Circle-Token: $MyToken" --request GET "https://circleci.com/api/v2/pipeline/${PIPE_ID}/workflow"|jq -r --arg WF_NAME "${WF_NAME}" '.items[]|select(.status == "on_hold" or .status == "running") | select(.name == $WF_NAME) | .id' >> WF_to_cancel.txt
done
fi
## Cancel any currently running/on_hold workflow with the same name
if [ -s WF_to_cancel.txt ]; then
echo "Cancelling the following workflow(s):"
cat WF_to_cancel.txt
while read WF_ID;
do
curl --header "Circle-Token: $MyToken" --request POST https://circleci.com/api/v2/workflow/$WF_ID/cancel
done < WF_to_cancel.txt
## Allowing some time to complete the cancellation
sleep 2
else
echo "Nothing to cancel"
fi
In case you wish to auto-cancel redundant builds regardless of the user, replace:
Is there any way to generate a token that only has access to a specific project? If I have multiple projects in CircleCI and generate a key it’d effectively give people access to all of my projects, even if they only have access to certain projects
I think this is a business choice that CircleCi made to make more money… I also think it is ridiculous that I have to pay for these redundant builds for default branch that do not add any value to our testing process!
To CircleCi admins: please make a checkbox for cancelling redundant builds for default branch also
Is there a way to list the branches that we’d want to not auto-cancel? Right now, it’s the default branch, however integration branches, particularly the top-level integration branch (like ‘develop’), would want all builds to continue.
At the moment, this is not possible. But what you can do is disable the feature altogether, and use the same logic as that in this workaround, with a condition on the branch name.
You could, for example, add a case statement at the start of the script to condition its execution on the CIRCLE_BRANCH built-in environment variable having specific values.
for bitbucket repositories, you need to use /bb/ instead of /gh/ when making the Circleci API calls;
Here’s the updated version of the script that worked for me; please note to replace use /bb/ or /gh/ depending on your integration:
#!/bin/bash
## Get the name of the workflow and the related pipeline number
curl --header "Circle-Token: $MyToken" --request GET "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}" -o current_workflow.json
WF_NAME=$(jq -r '.name' current_workflow.json)
CURRENT_PIPELINE_NUM=$(jq -r '.pipeline_number' current_workflow.json)
## Get the IDs of pipelines created by the current user on the same branch. (Only consider pipelines that have a pipeline number inferior to the current pipeline)
PIPE_IDS=$(curl --header "Circle-Token: $MyToken" --request GET "https://circleci.com/api/v2/project/bb/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pipeline?branch=$CIRCLE_BRANCH"|jq -r --argjson CURRENT_PIPELINE_NUM "$CURRENT_PIPELINE_NUM" '.items[] | select(.state == "created") | select(.number < $CURRENT_PIPELINE_NUM)|.id')
## Get the IDs of currently running/on_hold workflows that have the same name as the current workflow, in all previously created pipelines.
if [ ! -z "$PIPE_IDS" ]; then
for PIPE_ID in $PIPE_IDS
do
curl --header "Circle-Token: $MyToken" --request GET "https://circleci.com/api/v2/pipeline/${PIPE_ID}/workflow"|jq -r --arg WF_NAME "${WF_NAME}" '.items[]|select(.status == "running") | select(.name == $WF_NAME) | .id' >> WF_to_cancel.txt
done
fi
## Cancel any currently running/on_hold workflow with the same name
if [ -s WF_to_cancel.txt ]; then
echo "Cancelling the following workflow(s):"
cat WF_to_cancel.txt
while read WF_ID;
do
curl --header "Circle-Token: $MyToken" --request POST https://circleci.com/api/v2/workflow/$WF_ID/cancel
done < WF_to_cancel.txt
## Allowing some time to complete the cancellation
sleep 2
else
echo "Nothing to cancel"
fi
Following up on this, we now successfully enabled auto cancellation for redundant build except for a set of protected branches (here: master and production)
set -e
PROTECTED_BRANCHES="master production"
# Check if the current branch is protected
if [[ " $PROTECTED_BRANCHES " =~ " $CIRCLE_BRANCH " ]]; then
echo "Current branch is protected. No workflows will be canceled."
exit 0
fi
WORKFLOW_INFO=$(curl -s --header "Circle-Token: $CIRCLE_CI_API_TOKEN" \
--request GET \
"https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}")
WF_NAME=$(echo "$WORKFLOW_INFO" | jq -r '.name')
CURRENT_PIPELINE_NUM=$(echo "$WORKFLOW_INFO" | jq -r '.pipeline_number')
PIPE_IDS=$(curl -s --header "Circle-Token: $CIRCLE_CI_API_TOKEN" \
--request GET \
"https://circleci.com/api/v2/project/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pipeline?branch=$CIRCLE_BRANCH" | \
jq -r --argjson CURRENT_PIPELINE_NUM "$CURRENT_PIPELINE_NUM" \
'if .items and .items != [] then .items[] | select(.state == "created") | select(.number < $CURRENT_PIPELINE_NUM)|.id else empty end')
if [ ! -z "$PIPE_IDS" ]; then
echo "Found pipelines to check: $PIPE_IDS"
for PIPE_ID in $PIPE_IDS; do
WORKFLOWS_TO_CANCEL=$(curl -s --header "Circle-Token: $CIRCLE_CI_API_TOKEN" \
--request GET \
"https://circleci.com/api/v2/pipeline/${PIPE_ID}/workflow" | \
jq -r --arg WF_NAME "$WF_NAME" \
'.items[] | select(.status == "running" or .status == "on_hold") | select(.name == $WF_NAME) | .id')
if [ ! -z "$WORKFLOWS_TO_CANCEL" ]; then
echo "Cancelling the following workflow(s) in pipeline $PIPE_ID: $WORKFLOWS_TO_CANCEL"
for WF_ID in $WORKFLOWS_TO_CANCEL; do
curl -s --header "Circle-Token: $CIRCLE_CI_API_TOKEN" \
--request POST \
"https://circleci.com/api/v2/workflow/${WF_ID}/cancel"
done
fi
done
else
echo "No redundant pipelines found to cancel."
fi