-
Notifications
You must be signed in to change notification settings - Fork 4
Project Status Sync
This page documents how our project-status.yml workflow works at the org level, how the GitHub App that powers it was created, and how to maintain or update it.
It supports automatic status transitions on the org project board configured per-repo.
Before enabling this automation on a repo, you MUST disable GitHub's built-in project workflows on the org project board. Leaving them on will cause race conditions, duplicate status changes, and items being re-added immediately after our bot removes them.
GitHub Projects ships with a set of built-in workflows (Project settings β Workflows) that fire on the same events our bot listens to. When both run, the last writer wins β and there's no guarantee which one finishes first. Symptoms include statuses flipping back and forth, items reappearing on the board after closure without merge, and inconsistent behavior across repos.
| Built-in Workflow | Why It Conflicts |
|---|---|
| Item added to project | Our bot adds items itself in step 7 and sets the contextually correct status. The built-in races against this and may overwrite our status. |
| Item reopened | Our bot maps reopened to "In Progress" for both Issues and PRs. Direct overlap. |
| Item closed | Our bot distinguishes merged PRs (β Done) from closed-without-merge (β REMOVE from board). The built-in just sets a status and will undo our removal or fight our Done assignment. |
| Pull request merged | Already handled by our closed event branch with PR_MERGED=true. Direct duplicate. |
| Auto-add to project | Our step 7 adds missing items explicitly. Built-in auto-add creates redundant add attempts. |
| Built-in Workflow | Notes |
|---|---|
| Auto-assign sub-issues to project | Fires on parent/child issue relationships, not the status events our bot handles. Our bot picks up sub-issues from there. β Keep this on. |
| Auto-archive items | Soft conflict only β operates on staleness, not events. It won't race the bot but may archive items the bot later tries to update. Leave on only if you actively want stale-item archiving. |
Go to the org project board β β― menu β Workflows. Confirm only "Auto-assign sub-issues to project" (and optionally "Auto-archive items") show as Enabled. Everything else listed above should be Disabled.
β Note on undocumented oversight: This step was missing from earlier versions of this wiki. If you set up the bot in any repo before this update, audit your project Workflows page now.
This automation is powered by:
| Component | Purpose |
|---|---|
| GitHub App | Provides a secure, short-lived org token for automation |
| Project Status Workflow | Sets the correct board status for Issues and Pull Requests |
| Project v2 GraphQL API | Allows updating Status fields programmatically |
The workflow cannot run with personal tokens and cannot run with standard repo secrets β it requires a GitHub App with org-level permissions.
The app must be created at the organization level, not per-repo.
| Category | Permission |
|---|---|
| Repository Permissions | Contents: Read, Metadata: Read |
| Organization Permissions | Projects: Read/Write, Members: Read |
| User Permissions | None required |
The critical permission is Org Projects: Read/Write, which is required to update Project v2 fields.
The GitHub App does not need a webhook URL for this workflow.
It is used only to generate tokens for GitHub Actions.
After creation, install it to the entire organization.
This gives the app access to read/write our shared org project board.
Each repo using this automation must define:
| Secret Name | What It Stores |
|---|---|
APP_PEM |
PEM private key downloaded from the GitHub App |
Only owners or approved maintainers should add this secret.
| Variable Name | What It Stores |
|---|---|
APP_ID |
The GitHub App's numeric ID (found on app settings page) |
This is a variable, not a secret β it is not sensitive.
The workflow references the org project board that this repo lives on. This value is repo-specific β set it to whatever project number you're using for that repo's board.
env:
PROJECT_NUMBER: 7 # Replace 7 with your project's numberFind the project number in the project URL: https://github.com/orgs/<org>/projects/<NUMBER>.
The number is hardcoded in the workflow file directly. If you ever need to point a repo at a different project, edit the PROJECT_NUMBER line in that repo's project-status.yml. (Promoting this to a repo variable is a possible future improvement but not currently done.)
A short-lived authentication token is created with:
uses: actions/create-github-app-token@v2This produces a token valid for about 10 minutes.
It grants permission to update Project v2 fields securely.
GraphQL API calls are then executed using:
gh api graphqlThese calls:
- Fetch project/field IDs
- Add/remove items
- Update Status field values
If authentication fails, no project updates are possible, which is expected behavior.
The workflow listens for:
| Event Type | Examples |
|---|---|
| Issue Events | opened, assigned, closed, reopened |
| Pull Request Events | opened, ready, draft, closed, merged |
| Review Events | changes requested |
It translates these into board statuses:
| Event | Status |
|---|---|
| Issue opened | Backlog |
| Issue assigned | In Progress |
| PR opened (not draft) | In Review |
| PR draft created or re-drafted | In Progress |
| Changes requested | Blocked |
| PR merged | Done |
| PR closed without merging | REMOVE from board |
β Removal happens only when PRs close without merging to prevent clutter.
The workflow triggers on pull_request_target (not pull_request) so it works when contributors open PRs from forks. This was added after deployment when a student forking the repo had their PRs ignored by the bot. pull_request_target runs in the context of the base repo and has access to repo secrets, which is what the GitHub App token generation step needs. Standard pull_request would not have that access for forked PRs.
β Active bug: The
case "$EVENT_NAME"statement in the decision step matchespull_request, but with thepull_request_targettrigger the actual value of$EVENT_NAMEispull_request_target. This means the PR branch of the logic currently never fires β only theissuesandpull_request_reviewbranches do. To fix, change the case match topull_request_target)(or match both withpull_request|pull_request_target)).
Every repo using project-status.yml also ships with auto-assign.yml, a small companion workflow that assigns the PR author as an assignee on their own PR. This is unrelated to the project board β it sets the repo-level assignee on the PR itself, which is what the status sync bot then keys off of for the "assigned β In Progress" transition on linked issues.
name: Auto-assign PRs to author
on:
pull_request_target:
types: [opened, ready_for_review]It uses the same GitHub App token mechanism (APP_ID variable + APP_PEM secret) as the status sync workflow, and also uses pull_request_target for the same fork-support reason.
Don't confuse this with GitHub's built-in "Auto-assign sub-issues to project" workflow on the project board settings page. They're completely different:
Custom auto-assign.yml
|
Built-in "Auto-assign sub-issues to project" | |
|---|---|---|
| Where it lives |
.github/workflows/auto-assign.yml in each repo |
Project board β β― β Workflows |
| What it assigns | PR author as PR assignee | Sub-issues onto the project board |
| Touches the board? | No | Yes |
| Conflicts with status sync bot? | No | No (safe to keep on) |
Both are safe to keep enabled alongside the status sync bot.
Update if:
- Status column names change
- New columns are added/removed
- Project number changes
- GitHub App permissions change
- Built-in project workflows are re-enabled (see Critical section above)
| Task | Who Can Do It |
|---|---|
| Rotate App PEM Keys | Org Owner only |
| Add/remove workflow in repos | Maintainer or Owner |
| Update status labels | Instructor or Maintainer |
| Clean orphaned board items | Maintainer |
| Audit built-in project workflows | Maintainer or Owner |
| Problem | Cause | Fix |
|---|---|---|
| Status flips back after bot sets it | Built-in workflow re-enabled | Disable conflicting built-ins (see Critical section) |
| Item reappears after close-without-merge | Built-in "Item closed" re-adding it | Disable built-in "Item closed" workflow |
| PR events do nothing |
case matches pull_request but trigger is pull_request_target, so $EVENT_NAME never matches |
Change case match to pull_request_target)
|
Missing STATUS_FIELD_ID
|
Board renamed | Update board or workflow |
| Token forbidden | App not installed org-wide | Reinstall |
| Removal fails | Item never added | Safe to ignore |
| GraphQL field missing | GitHub API changed | Rerun query & update |
-
[Status Sync Workflow](https://github.com/SeattleColleges/nsc-events-fullstack/blob/main/.github/workflows/project-status.yml)
β Previous version of this wiki linked to
auto-assign.yml. Verify the correct path before publishing. -
[GitHub App Settings](https://github.com/organizations/SeattleColleges/settings/apps)
Home β’ New Student Onboarding β’ Guides β’ Projects β’ Code of Conduct β’ FAQ
Last updated: 12/7/2025