Generate a GitHub Actions workflow file to automatically synchronize Issue/PR status changes to the project's Status field. A manual bulk correction mode is also provided.
Prerequisites
- The target GitHub Project is linked to the repository
ghCLI is installed and authenticated (withprojectscope)
Flow
Confirm the execution mode with the user:
- Automatic sync setup — Generate a GitHub Actions workflow (recommended for first-time setup)
- Manual bulk correction — Correct current inconsistencies in bulk (for spot execution)
Mode A: Automatic sync setup
Step A-1: Retrieve project information
# オーナーを取得
gh repo view --json owner -q '.owner.login'
# プロジェクト番号を確認
gh project list --owner <owner> --format json
Confirm the target project number with the user.
Step A-2: Explain the authentication secret
To access the Projects API from GitHub Actions, GITHUB_TOKEN is insufficient, so one of the following is required:
Method 1: Personal Access Token (for individuals/small scale)
- GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Required scopes:
project(read/write) +issues(read/write) +pull_requests(read/write) - Repository Settings → Secrets and variables → Actions →
PROJECT_TOKENregistered as
Method 2: GitHub App token (for Organizations, recommended)
- Create a GitHub App and grant the Organization
Projects: Read and writepermission - Within the workflow, use
actions/create-github-app-tokento generate a token
Step A-3: Generate a GitHub Actions workflow
Fandhe-AI/actions/project-sync Use Composite Action..github/workflows/project-sync.yml Generate
Reference method (in-house actions use @latest, third-party actions use pinned SHA):
Fandhe-AI/actions/*(in this skill, project-sync) is an in-house action managed and reviewed within the organization, so @latest tag reference is used (owner's decision). On the other hand, third-party actions (actions/create-github-app-token , etc.) are pinned to verified commit SHAs rather than movable references such as @main / @vN etc. The method of dynamically fetching the latest SHA and embedding it on every generation would propagate the upstream code as-is to the adopter if the upstream had been compromised or unintentionally modified at the time of fetching. Therefore,when generating workflows, use the following reviewed pinned SHAs as constants, and do not perform dynamic fetching of the latest SHA:
| Action | Pinned SHA | Corresponding version |
|---|---|---|
actions/create-github-app-token |
fee1f7d63c2ff003460e3d139729b119787bc349 |
v2.2.2 |
The above SHAs are values pinned after actually fetching and scrutinizing the code contents (action.ymland the full text of referenced scripts) at the time of adoption. Only when the SHA needs to be updated (do not perform this on every generation), use the following procedure to checkthe changes themselvesAfter carefully reviewing, update the constants and workflow examples in this file (the scope is third-party actions only.Fandhe-AI/actions/project-sync は @latest out of scope for the SHA update procedure as reference):
# 更新候補の最新 SHA を取得(更新作業時のみ実行。対象例: actions/create-github-app-token)
gh api repos/actions/create-github-app-token/commits/main --jq '.sha'
# 旧 SHA との差分パッチ(変更内容そのもの)を取得して精査する。ファイル名一覧だけでは不十分
gh api repos/actions/create-github-app-token/compare/<旧SHA>...<新SHA> --jq '.files[] | {filename, patch}'
# Action のエントリーポイント(action.yml)と参照される全スクリプトを新 SHA 時点の
# 内容で取得し、action が実行するコマンド・ダウンロードするバイナリ等に不審な変更が
# ないか実際に読んで確認する
gh api repos/actions/create-github-app-token/contents/action.yml?ref=<新SHA> --jq '.content' | base64 -d
Actually read the diff patch and action.yml(as well as the full text of the referenced scripts), and manually verify that there are no unintended changes or suspicious command additions. If possible, also verify the signature and release provenance (gh attestation verify etc.). Only when verification is obtained, update the fixed SHA table above and the comment (corresponding version) on the uses: lines in the subsequent workflow examples accordingly.Fandhe-AI/actions/project-sync は @latest This review procedure is unnecessary for references (on the premise that they are managed and reviewed within the organization, and the owner has accepted this).
When using a PAT:
name: Project Sync
on:
issues:
types: [opened, closed, reopened]
pull_request:
types: [opened, closed, ready_for_review, review_requested]
permissions:
contents: read
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Sync project status
uses: Fandhe-AI/actions/project-sync@latest
with:
project-number: '<number>'
project-owner: '<owner>'
token: ${{ secrets.PROJECT_TOKEN }}
When using a GitHub App (recommended):
name: Project Sync
on:
issues:
types: [opened, closed, reopened]
pull_request:
types: [opened, closed, ready_for_review, review_requested]
permissions:
contents: read
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Generate token
id: token
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: '<owner>'
- name: Sync project status
uses: Fandhe-AI/actions/project-sync@latest
with:
project-number: '<number>'
project-owner: '<owner>'
token: ${{ steps.token.outputs.token }}
When using custom Status option names:
If the Status option names differ from the defaults (Todo / In Progress / In Review / Done), specify them in inputs:
- name: Sync project status
uses: Fandhe-AI/actions/project-sync@latest
with:
project-number: '<number>'
project-owner: '<owner>'
token: ${{ secrets.PROJECT_TOKEN }}
status-todo: 'バックログ'
status-in-progress: '作業中'
status-in-review: 'レビュー中'
status-done: '完了'
Step A-4: Check the status mapping
Default mapping for the workflow to be generated:
| Event | Action | Status |
|---|---|---|
| Issue | opened | Todo |
| Issue | closed | Done |
| Issue | reopened | Todo |
| PR | opened | In Progress |
| PR | ready_for_review | In Review |
| PR | review_requested | In Review |
| PR | closed (merged) | Done |
| PR | closed (not merged) | Todo |
Customize the mapping according to the user's request.
Step A-5: Place the workflow file
mkdir -p .github/workflows
# ワークフローファイルを .github/workflows/project-sync.yml に書き出す
Guide the user to commit and push.
Mode B: Manual bulk correction
Compare the current state of the project and Issues/PRs, and correct inconsistencies in bulk. Use this for the initial correction after setting up automatic synchronization, or to reflect manual changes.
Step B-1: Retrieve project items
gh project item-list <number> \
--owner <owner> \
--format json \
--limit 999
Step B-2: Check the current state of Issues/PRs
For items of type Issue/PR, check the current state:
gh issue view <issue-url> --json state,labels,assignees
gh pr view <pr-url> --json state,isDraft,reviewRequests,merged
Step B-3: Detect state mismatches
Detect the following mismatch patterns:
- Issue is closed but the project Status is not Done → update to Done
- Issue is open but the project Status is Done → update to Todo
- PR is merged but Status is not Done → update to Done
- PR has a review request but the Status is not In Review → update to In Review
Step B-4: Detect Issues/PRs not added to the repository
gh issue list --state open --json number,url,title --limit 999
gh pr list --state open --json number,url,title --limit 999
Compare with the project's item URLs to identify those not yet added.
Step B-5: Confirm the synchronization details with the user
Display the detection results:
## 同期内容
### ステータス更新(N 件)
- #42: ソーシャルログイン — Status: In Progress → Done(Issue closed)
- #45: バグ修正 — Status: Done → Todo(Issue reopened)
- #50: リファクタリング PR — Status: In Progress → In Review(レビュー依頼済み)
### 新規追加(M 件)
- #55: 新機能リクエスト (Issue)
- #56: ドキュメント更新 PR
実行しますか?
Step B-6: Execute synchronization
# フィールドメタデータを取得
gh project field-list <number> --owner <owner> --format json
# ステータス更新
gh project item-edit \
--id <item-id> \
--field-id <status-field-id> \
--project-id <project-id> \
--single-select-option-id <option-id>
# 新規追加
gh project item-add <number> \
--owner <owner> \
--url <issue-or-pr-url> \
--format json
Step B-7: Report the synchronization results
| Operation | Count |
|---|---|
| Status updates | N items |
| Newly added | M items |
| No changes | K items |
Notes
- Authentication: For access to the Projects API from GitHub Actions,
GITHUB_TOKENis insufficient. A PAT or GitHub App token is required - PAT expiration: fine-grained PATs last up to 1 year. Regular rotation is recommended
- Combined use with built-in workflows:
project-initIf the built-in workflow (closed→Done, merged→Done) has already been enabled, it will fire twice along with the Actions workflow, but since it updates to the same value there is no real harm - PR lifecycle: The built-in workflow only supports closed/merged. opened→In Progress and review_requested→In Review can only be automated via Actions
- Private repository: In the org's Settings → Actions → General, sharing Actions from private repositories must be allowed
- Manual correction mode must always obtain user confirmation before syncing
- Items of type DraftIssue are excluded from sync (because no real Issue exists)
- Requires network (mainly via API. See the "Running in a sandbox environment" section below)
- Third-party actions must always be pinned by commit SHA:
@main/@vNMutable references such as these are not generated. This is to avoid supply chain risks where upstream tag re-pointing or branch modification flows in unverified (when updating the SHA, check the diff before updating).Fandhe-AI/actions/*(in-house action) is@latestreferenced as(because it is managed and reviewed within the organization. Owner's decision) - permissions must be explicitly specified with least privilege: at the workflow level,
contents: readmust be explicitly specified. The sync process itself operates under the permissions ofPROJECT_TOKEN/ the GitHub App token, soGITHUB_TOKENno additional permissions to are needed
Verification
After Mode A completes: .github/workflows/project-sync.yml exists and the YAML is correctly written. In addition, confirm the following:
uses:the reference style of the lines is correct (Fandhe-AI/actions/*は@latest, everything else (third-party) is pinned by a 40-character hexadecimal commit SHA. For third-party, actively verify that no@main・@v2.2.2・@masteror arbitrary branch names or other mutable references remain):f=.github/workflows/project-sync.yml total=$(grep -c 'uses:' "$f") fandhe_total=$(grep -cE 'uses:\s*Fandhe-AI/actions/' "$f") fandhe_ok=$(grep -cE 'uses:\s*Fandhe-AI/actions/[^@[:space:]]+@latest([[:space:]]|#|$)' "$f") other_total=$((total - fandhe_total)) other_pinned=$(grep -E 'uses:' "$f" | grep -vE 'Fandhe-AI/actions/' \ | grep -cE 'uses:\s*[^@[:space:]]+@[0-9a-f]{40}([[:space:]]|#|$)' || true) if [ "$total" -eq 0 ]; then echo "NG: 検証対象の uses: 行が見つからない(workflow 生成に失敗している可能性)" >&2 exit 1 elif [ "$fandhe_total" -eq "$fandhe_ok" ] && [ "$other_total" -eq "$other_pinned" ]; then echo OK else echo "NG: 参照方式が不正な uses: 行がある(fandhe_total=${fandhe_total}, fandhe_ok=${fandhe_ok}, other_total=${other_total}, other_pinned=${other_pinned})" >&2 exit 1 fiOKis output (uses:1 or more lines exist, andFandhe-AI/actions/*all lines are@latest, all other lines are pinned by 40-character SHA).NG:If this is output, review the generated workflow contentpermissionsis explicitly specified:grep -c 'permissions:' .github/workflows/project-sync.ymlis 1 or moresyncto the jobtimeout-minutesis set:grep -n 'timeout-minutes' .github/workflows/project-sync.ymlmatches 1 or more lines (missing is a CI workflow convention violation, P1)
Complete once the initial trigger can be confirmed in the GitHub Actions run history after committing and pushing.
After Mode B completes: In the result table of Step B-7, "no changes: 0 or more" is displayed. Confirm the final state with the following:
gh project item-list <number> --owner <owner> --format json --limit 999
Running in a sandbox environment
This skill requires GitHub operations over the network (committing and pushing the sync workflow,gh project item-edit bulk corrections such as these). The relevant commands are executed with the sandbox disabled on a per-command basis. It cannot be executed in an environment where network blocking cannot be lifted.