In my previous post, I discussed how to use GitHub CLI with GitHub Actions for project automaton. In this post, let’s look at moving issues to the “Done” status in GitHub Projects. This transition happens automatically when the issues are closed.
The Power of Automation in Project Management
Keeping project boards up-to-date can be a tedious task, especially for teams managing multiple issues across various projects. This is why I recommend using GitHub Actions with GitHub CLI. They can automate a number of potentially manual processes. This saves time and reduces the risk of human error.
Benefits of this automation
- Reducing manual effort: Reduces the manual need to move issues to “Done” when they’re closed.
- Real-time Project updates: Project board always reflects the current state of your issues.
- Time Saving: No manual effort required, team members can focus on more important tasks.
- Consistency: Ensures all closed issues are consistently marked as “Done”.
Setting up the Workflow
The automation of moving between statuses is all done via a GitHub workflow. This workflow triggers whenever an issue is closed. It then moves the issue to the “Done” status in your GitHub Project.
The full workflow:
name: Move Issue to Done When Closed
on:
issues:
types: [closed]
jobs:
move-to-done:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.SECRET_PAT }}
PROJECT_OWNER: thomasthorntoncloud
PROJECT_NUMBER: 1
ISSUE_NUMBER: ${{ github.event.issue.number }}
steps:
- name: Move issue to Done
run: |
# Get project ID
PROJECT_ID=$(gh project view $PROJECT_NUMBER --owner $PROJECT_OWNER --format json | jq -r '.id')
echo "Project ID: $PROJECT_ID"
# Get all fields
FIELDS=$(gh project field-list $PROJECT_NUMBER --owner $PROJECT_OWNER --format json)
echo "All fields:"
echo "$FIELDS" | jq '.'
STATUS_FIELD=$(echo "$FIELDS" | jq -r '.fields[] | select(.name == "Status")')
STATUS_FIELD_ID=$(echo "$STATUS_FIELD" | jq -r '.id')
DONE_OPTION_ID=$(echo "$STATUS_FIELD" | jq -r '.options[] | select(.name == "Done") | .id')
echo "STATUS_FIELD_ID: $STATUS_FIELD_ID"
echo "DONE_OPTION_ID: $DONE_OPTION_ID"
# Find the item ID for the closed issue
ITEM_ID=""
echo "Fetching items..."
ITEMS_RESPONSE=$(gh project item-list $PROJECT_NUMBER --owner $PROJECT_OWNER --format json --limit 10000)
# Extract the item ID if the issue is found
ITEM_ID=$(echo "$ITEMS_RESPONSE" | jq -r --arg ISSUE_NUMBER "$ISSUE_NUMBER" '.items[] | select(.content.number == ($ISSUE_NUMBER | tonumber)) | .id')
if [ -n "$ITEM_ID" ]; then
echo "Item found with ID: $ITEM_ID"
else
echo "Error: Could not find the issue in the project. Skipping status update."
exit 0
fi
echo "ITEM_ID: $ITEM_ID"
# Set the Status to Done
echo "Updating item status to Done..."
RESULT=$(gh project item-edit $PROJECT_NUMBER \
--id "$ITEM_ID" \
--project-id "$PROJECT_ID" \
--field-id "$STATUS_FIELD_ID" \
--single-select-option-id "$DONE_OPTION_ID" \
--format json)
if echo "$RESULT" | jq -e '.id' > /dev/null; then
echo "Issue successfully moved to Done status."
else
echo "Error: Updating issue status: $RESULT"
exit 1
fi
Breaking down the workflow
1. Retrieve Project information
Get the project ID and field information:
PROJECT_ID=$(gh project view $PROJECT_NUMBER --owner $PROJECT_OWNER --format json | jq -r '.id')
FIELDS=$(gh project field-list $PROJECT_NUMBER --owner $PROJECT_OWNER --format json)
2. Retrieving the “status” field and “Done” option
Getting all fields and then retrieving the required field & option:
FIELDS=$(gh project field-list $PROJECT_NUMBER --owner $PROJECT_OWNER --format json)
echo "All fields:"
echo "$FIELDS" | jq '.'
STATUS_FIELD=$(echo "$FIELDS" | jq -r '.fields[] | select(.name == "Status")')
STATUS_FIELD_ID=$(echo "$STATUS_FIELD" | jq -r '.id')
DONE_OPTION_ID=$(echo "$STATUS_FIELD" | jq -r '.options[] | select(.name == "Done") | .id')
echo "STATUS_FIELD_ID: $STATUS_FIELD_ID"
echo "DONE_OPTION_ID: $DONE_OPTION_ID"
3. Finding the recently closed Issue in the Project
Now that we have the required information from above, time to find the required closed issue within the project:
ITEMS_RESPONSE=$(gh project item-list $PROJECT_NUMBER --owner $PROJECT_OWNER --format json --limit 10000)
# Extract the item ID if the issue is found
ITEM_ID=$(echo "$ITEMS_RESPONSE" | jq -r --arg ISSUE_NUMBER "$ISSUE_NUMBER" '.items[] | select(.content.number == ($ISSUE_NUMBER | tonumber)) | .id')
4. Updating the Issue Status to Done
Updating the Issue Status to Done within the GitHub Project:
RESULT=$(gh project item-edit $PROJECT_NUMBER \
--id "$ITEM_ID" \
--project-id "$PROJECT_ID" \
--field-id "$STATUS_FIELD_ID" \
--single-select-option-id "$DONE_OPTION_ID" \
--format json)
The workflow in GitHub
Lets look what happens within GitHub when I now close an issue that is on my GitHub project board.
- When I close the issue, this workflow starts:

- Within the issue, when the above is completed – it has moved from “ToDo” to “Done” within my project board:

Further customisation of this workflow
This workflow can be used as a template to assist with more project automation, including:
- Move issues to different statuses based on labels or other criteria
- Automatically assign team members when issues move to certain statuses
- Move issues to the board when the issue has been reopened
Wrapping up
We have automated the movement of closed issues to the “Done” status. This automation represents another step towards a more efficient project management process. This GitHub Actions workflow works with the power of GitHub CLI. It demonstrates that simple automations can significantly improve your team’s productivity. These automations also enhance project visibility.
Remember, the key to effective project management is not just about tracking issues. It’s about ensuring your tools and processes work for you, not the other way around. With GitHub Actions and CLI, there are vast possibilities for customisation and automation. Experiment and find what works best for your team!