Skip to content

Project Status Sync

Jesse edited this page May 26, 2026 · 2 revisions

Project Status Sync (GitHub App + IssueOps)

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.


⚠ CRITICAL: Disable Conflicting Built-in Project Workflows

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.

Why this matters

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.

Disable these built-in workflows

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.

Safe to leave enabled

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.

How to verify

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.


Overview

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.


GitHub App Setup

The app must be created at the organization level, not per-repo.

Required App Permissions

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.

Webhook Requirements

The GitHub App does not need a webhook URL for this workflow.
It is used only to generate tokens for GitHub Actions.

Install the App

After creation, install it to the entire organization.
This gives the app access to read/write our shared org project board.


Required Repository Configuration

Each repo using this automation must define:

1) Secret

Secret Name What It Stores
APP_PEM PEM private key downloaded from the GitHub App

Only owners or approved maintainers should add this secret.

2) Variable

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.

3) Project Number

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 number

Find 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.)


How the Workflow Uses the App

A short-lived authentication token is created with:

uses: actions/create-github-app-token@v2

This 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 graphql

These calls:

  • Fetch project/field IDs
  • Add/remove items
  • Update Status field values

If authentication fails, no project updates are possible, which is expected behavior.


Status Logic (How Status Changes Work)

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.

Why we use pull_request_target

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 matches pull_request, but with the pull_request_target trigger the actual value of $EVENT_NAME is pull_request_target. This means the PR branch of the logic currently never fires — only the issues and pull_request_review branches do. To fix, change the case match to pull_request_target) (or match both with pull_request|pull_request_target)).


Companion Workflow: Auto-assign PRs to Author

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.

Distinguishing this from GitHub's built-in workflow

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.


Basic Maintenance Guide

When to Update the Workflow

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)

Maintenance Tasks

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

Common Problems & Fixes

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

Links to the Actual Files


Clone this wiki locally