From 7418f28dbf88917b3a0de1c076dfd8dec45a4185 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 9 May 2026 10:02:44 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(repo-maintenance):=20Quality=20Gate=20?= =?UTF-8?q?=E5=BF=85=E9=A0=88=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=81=AE?= =?UTF-8?q?=20fallback=20=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=82=92=E9=85=8D=E5=B8=83=E5=AF=BE=E8=B1=A1=E3=81=AB?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setup-team-protection.sh はブランチ保護に "Quality Gate" を必須として登録するが、 ci.yml が paths フィルタでスキップされたり、claude[bot] / dependabot[bot] の GITHUB_TOKEN による push でワークフローが発火しなかった場合、Quality Gate が 報告されず PR が "Expected — Waiting for status to be reported" のまま blocked になる。 この状態を 3 時間ごとの n8n resolveGitConflict 監視が「CI 失敗」として誤検出し、 @claude PRがbaseにマージする際にCIが落ちています を誤投稿する原因になっていた (例: https://github.com/Elu-co-jp/prog-insight/pull/129)。 - templates/workflows/quality-gate-fallback.yml をテンプレート化 - repo-maintenance.md section 3.22 のマネージドファイルに追加し、 config 管理下の全リポジトリへ自動配布されるようにする - repo-maintenance.md に section 3.5.0.2 (Quality Gate Fallback Consistency Check) を追加し、ブランチ保護に Quality Gate がある repo で fallback 未配置時に警告 n8n 側の修正は keito4-org/n8n_custom_node#1857 で対応。 --- .claude/commands/repo-maintenance.md | 70 ++++++++++++++---- templates/workflows/quality-gate-fallback.yml | 74 +++++++++++++++++++ 2 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 templates/workflows/quality-gate-fallback.yml diff --git a/.claude/commands/repo-maintenance.md b/.claude/commands/repo-maintenance.md index 0000f21a..05f7b344 100644 --- a/.claude/commands/repo-maintenance.md +++ b/.claude/commands/repo-maintenance.md @@ -967,6 +967,42 @@ fi - 🔧 scheduled-maintenance.yml を配置しました - ⚠️ `CLAUDE_CODE_OAUTH_TOKEN` シークレットの設定が必要です +### 3.5.0.2 Quality Gate Fallback Consistency Check + +ブランチ保護で `Quality Gate` を必須チェックに登録している場合、対応する fallback ワークフローが配置されているか確認する。 + +**背景:** +`setup-team-protection.sh` はブランチ保護に `Quality Gate` を必須として登録するが、ci.yml が `paths` フィルタでスキップされたり、ci.yml 自体が無い / Dependabot や `claude[bot]` の GITHUB_TOKEN による push でワークフローが発火しなかった場合、`Quality Gate` チェックが報告されず、PR が `Expected — Waiting for status to be reported` のまま blocked になる。 +この状態を 3 時間ごとの n8n `resolveGitConflict` 監視が「CI 失敗」として誤検出し、`@claude PRがbaseにマージする際にCIが落ちています` を誤投稿する原因となっていた。 + +**確認ロジック:** + +```bash +REQUIRED_CHECKS="" +if gh api "repos/{owner}/{repo}/branches/main/protection/required_status_checks" >/dev/null 2>&1; then + REQUIRED_CHECKS=$(gh api "repos/{owner}/{repo}/branches/main/protection/required_status_checks" \ + --jq '.contexts // [] | join(",")' 2>/dev/null || echo "") +fi + +NEEDS_FALLBACK=false +if echo "$REQUIRED_CHECKS" | grep -q "Quality Gate"; then + # quality-gate-fallback.yml が無い場合は配置を提案 + if [ ! -f ".github/workflows/quality-gate-fallback.yml" ]; then + NEEDS_FALLBACK=true + fi +fi +``` + +**結果パターン:** + +| 状態 | 対応 | +| ------------------------------------- | ---------------------------------------------------- | +| Quality Gate 必須 + fallback 配置済み | ✅ スキップ | +| Quality Gate 必須 + fallback 未配置 | ⚠️ → full mode で `quality-gate-fallback.yml` を配置 | +| Quality Gate 非必須 | ⏭️ スキップ | + +`MODE` が `full` かつ未配置の場合は section 3.22 のマネージドファイル同期で自動配置される(`templates/workflows/quality-gate-fallback.yml`)。 + ### 3.5.1 CI Workflow Template Sync Check `templates/workflows/` のテンプレートと `.github/workflows/` の実ファイルを比較し、乖離を検出: @@ -2242,21 +2278,24 @@ fi **同期対象ファイルの分類:** -| カテゴリ | ファイル | 同期ポリシー | -| ------------ | ------------------------------------------- | ---------------------------- | -| マネージド | `.github/workflows/claude.yml` | 常に config の最新版で上書き | -| マネージド | `.github/workflows/claude-code-review.yml` | 常に config の最新版で上書き | -| マネージド | `.claude/hooks/block_git_no_verify.py` | 常に config の最新版で上書き | -| マネージド | `.claude/hooks/pre_git_quality_gates.py` | 常に config の最新版で上書き | -| マネージド | `.claude/hooks/post_git_push_ci.py` | 常に config の最新版で上書き | -| マネージド | `.claude/hooks/post_commit_adr_reminder.py` | 常に config の最新版で上書き | -| マネージド | `.claude/rules/development-standards.md` | 常に config の最新版で上書き | -| マネージド | `.claude/rules/git-conventions.md` | 常に config の最新版で上書き | -| マネージド | `.claude/rules/release-types.md` | 常に config の最新版で上書き | -| テンプレート | `.github/workflows/security.yml` | 差分表示 → 確認後に上書き | -| テンプレート | `.github/workflows/ci.yml` | 差分表示 → 確認後に上書き | -| テンプレート | `.github/ISSUE_TEMPLATE/*` | 欠落ファイルのみ追加 | -| テンプレート | `.github/pull_request_template.md` | 欠落時のみ追加 | +| カテゴリ | ファイル | 同期ポリシー | +| ------------ | --------------------------------------------- | ---------------------------- | +| マネージド | `.github/workflows/claude.yml` | 常に config の最新版で上書き | +| マネージド | `.github/workflows/claude-code-review.yml` | 常に config の最新版で上書き | +| マネージド | `.github/workflows/quality-gate-fallback.yml` | 常に config の最新版で上書き | +| マネージド | `.claude/hooks/block_git_no_verify.py` | 常に config の最新版で上書き | +| マネージド | `.claude/hooks/pre_git_quality_gates.py` | 常に config の最新版で上書き | +| マネージド | `.claude/hooks/post_git_push_ci.py` | 常に config の最新版で上書き | +| マネージド | `.claude/hooks/post_commit_adr_reminder.py` | 常に config の最新版で上書き | +| マネージド | `.claude/rules/development-standards.md` | 常に config の最新版で上書き | +| マネージド | `.claude/rules/git-conventions.md` | 常に config の最新版で上書き | +| マネージド | `.claude/rules/release-types.md` | 常に config の最新版で上書き | +| テンプレート | `.github/workflows/security.yml` | 差分表示 → 確認後に上書き | +| テンプレート | `.github/workflows/ci.yml` | 差分表示 → 確認後に上書き | +| テンプレート | `.github/ISSUE_TEMPLATE/*` | 欠落ファイルのみ追加 | +| テンプレート | `.github/pull_request_template.md` | 欠落時のみ追加 | + +`quality-gate-fallback.yml` は `setup-team-protection.sh` がブランチ保護に登録する `Quality Gate` 必須チェックとセットで配布する。ci.yml が paths フィルタ等でスキップされた場合に PR が `Expected — Waiting for status to be reported` のまま blocked にならないよう、Pass を emit する役割を持つ。 **マネージドファイル**: config リポジトリが正規のソースであり、プロジェクト側でカスタマイズしない前提のファイル。 **テンプレートファイル**: プロジェクト固有のカスタマイズが入る可能性があるため、差分確認を挟む。 @@ -2294,6 +2333,7 @@ fi MANAGED_FILES=( ".github/workflows/claude.yml" ".github/workflows/claude-code-review.yml" + ".github/workflows/quality-gate-fallback.yml" ".claude/hooks/block_git_no_verify.py" ".claude/hooks/pre_git_quality_gates.py" ".claude/hooks/post_git_push_ci.py" diff --git a/templates/workflows/quality-gate-fallback.yml b/templates/workflows/quality-gate-fallback.yml new file mode 100644 index 00000000..4800c083 --- /dev/null +++ b/templates/workflows/quality-gate-fallback.yml @@ -0,0 +1,74 @@ +# Quality Gate Fallback +# +# Required Status Check "Quality Gate" を必ず報告するための fallback ワークフロー。 +# +# 必要な背景: +# - setup-team-protection.sh はブランチ保護に "Quality Gate" を必須チェックとして登録する +# - ci.yml の Quality Gate ジョブが paths フィルタでスキップされた場合や、 +# ci.yml 自体が存在しない / 走らない場合、PR は永遠に +# "Expected — Waiting for status to be reported" のまま blocked になる +# - この fallback は ci.yml の実行可否をチェックし、走っていなければ Pass を emit する +# +# 動作: +# - ci.yml が走っている / 成功している場合 → 何もしない (本体の Quality Gate が優先) +# - ci.yml が走っていない場合 → Pass で Quality Gate を emit +# +# 使い方: +# .github/workflows/quality-gate-fallback.yml にコピーして配置 +# setup-team-protection.sh で "Quality Gate" を必須チェックに設定する場合は必須 +# +# 既知の制限: +# - GITHUB_TOKEN で push されたコミット (claude[bot] / dependabot[bot] が +# GITHUB_TOKEN で押した場合など) では `pull_request` イベントが発火しないため、 +# この fallback も実行されない。その場合は別途 PAT 経由の push か +# workflow_dispatch / repository_dispatch での再トリガーが必要。 +# +name: CI Fallback + +on: + pull_request: + branches: [main, master, pre-production, production] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-fallback + cancel-in-progress: true + +jobs: + quality-gate: + name: Quality Gate + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: Check if CI workflow ran + id: check + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + head_sha: context.sha, + per_page: 20, + }); + + const ciRun = runs.workflow_runs.find( + r => r.name === 'CI' && r.id !== context.runId + ); + + if (ciRun && ciRun.status !== 'completed') { + core.info(`CI workflow is running (${ciRun.html_url}), this fallback is not needed.`); + core.setOutput('ci_running', 'true'); + } else if (ciRun && ciRun.conclusion === 'success') { + core.info(`CI workflow already succeeded (${ciRun.html_url}).`); + core.setOutput('ci_running', 'true'); + } else { + core.info('CI workflow did not run for this commit. Providing fallback Quality Gate.'); + core.setOutput('ci_running', 'false'); + } + + - name: Pass (CI skipped) + if: steps.check.outputs.ci_running == 'false' + run: echo "Quality Gate passed (CI skipped — no code changes detected or workflow disabled)." From ee31617d7b511739ec9413a9418d78c8b947714b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 9 May 2026 10:28:52 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(quality-gate-fallback):=20actions:=20re?= =?UTF-8?q?ad=20=E6=A8=A9=E9=99=90=E3=81=A8=20try/catch=20=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prog-insight#132 で fallback ワークフローが 403 Resource not accessible by integration で失敗し、Quality Gate が fail として報告されることを確認。 原因: github.rest.actions.listWorkflowRunsForRepo は actions: read 権限を 要求するが、permissions ブロックには contents: read のみ宣言していた。 - permissions に actions: read を追加 (templates/ と .github/workflows/ 両方) - 権限不足や API 障害で listWorkflowRuns が失敗した場合は ci_running='true' 扱いにして fallback の Pass emit をスキップ。誤った Pass / Fail よりは Required check が未報告のまま残る方が安全 --- .github/workflows/quality-gate-fallback.yml | 40 ++++++++++------- templates/workflows/quality-gate-fallback.yml | 44 ++++++++++++------- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/.github/workflows/quality-gate-fallback.yml b/.github/workflows/quality-gate-fallback.yml index 61254f93..f1e6b45b 100644 --- a/.github/workflows/quality-gate-fallback.yml +++ b/.github/workflows/quality-gate-fallback.yml @@ -12,6 +12,7 @@ on: permissions: contents: read + actions: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-fallback @@ -28,26 +29,31 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | - const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - head_sha: context.sha, - per_page: 20, - }); + try { + const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + head_sha: context.sha, + per_page: 20, + }); - const ciRun = runs.workflow_runs.find( - r => r.name === 'CI' && r.id !== context.runId - ); + const ciRun = runs.workflow_runs.find( + r => r.name === 'CI' && r.id !== context.runId + ); - if (ciRun && ciRun.status !== 'completed') { - core.info(`CI workflow is running (${ciRun.html_url}), this fallback is not needed.`); + if (ciRun && ciRun.status !== 'completed') { + core.info(`CI workflow is running (${ciRun.html_url}), this fallback is not needed.`); + core.setOutput('ci_running', 'true'); + } else if (ciRun && ciRun.conclusion === 'success') { + core.info(`CI workflow already succeeded (${ciRun.html_url}).`); + core.setOutput('ci_running', 'true'); + } else { + core.info('CI workflow did not run for this commit. Providing fallback Quality Gate.'); + core.setOutput('ci_running', 'false'); + } + } catch (err) { + core.warning(`Could not query workflow runs (${err.status || err.message}). Skipping fallback.`); core.setOutput('ci_running', 'true'); - } else if (ciRun && ciRun.conclusion === 'success') { - core.info(`CI workflow already succeeded (${ciRun.html_url}).`); - core.setOutput('ci_running', 'true'); - } else { - core.info('CI workflow did not run for this commit. Providing fallback Quality Gate.'); - core.setOutput('ci_running', 'false'); } - name: Pass (CI skipped) diff --git a/templates/workflows/quality-gate-fallback.yml b/templates/workflows/quality-gate-fallback.yml index 4800c083..261e2b77 100644 --- a/templates/workflows/quality-gate-fallback.yml +++ b/templates/workflows/quality-gate-fallback.yml @@ -31,6 +31,7 @@ on: permissions: contents: read + actions: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-fallback @@ -47,26 +48,35 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | - const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - head_sha: context.sha, - per_page: 20, - }); + try { + const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + head_sha: context.sha, + per_page: 20, + }); - const ciRun = runs.workflow_runs.find( - r => r.name === 'CI' && r.id !== context.runId - ); + const ciRun = runs.workflow_runs.find( + r => r.name === 'CI' && r.id !== context.runId + ); - if (ciRun && ciRun.status !== 'completed') { - core.info(`CI workflow is running (${ciRun.html_url}), this fallback is not needed.`); + if (ciRun && ciRun.status !== 'completed') { + core.info(`CI workflow is running (${ciRun.html_url}), this fallback is not needed.`); + core.setOutput('ci_running', 'true'); + } else if (ciRun && ciRun.conclusion === 'success') { + core.info(`CI workflow already succeeded (${ciRun.html_url}).`); + core.setOutput('ci_running', 'true'); + } else { + core.info('CI workflow did not run for this commit. Providing fallback Quality Gate.'); + core.setOutput('ci_running', 'false'); + } + } catch (err) { + // 権限不足 (actions: read 未付与) や一時的な API 障害でクエリ失敗時は + // 安全側に倒す。"CI 実行中扱い" にして fallback の Pass emit をスキップし、 + // 本体の Quality Gate (ci.yml) の結果を待つ。Required check が + // 未報告のままになる可能性はあるが、誤った Pass / Fail よりは安全。 + core.warning(`Could not query workflow runs (${err.status || err.message}). Skipping fallback.`); core.setOutput('ci_running', 'true'); - } else if (ciRun && ciRun.conclusion === 'success') { - core.info(`CI workflow already succeeded (${ciRun.html_url}).`); - core.setOutput('ci_running', 'true'); - } else { - core.info('CI workflow did not run for this commit. Providing fallback Quality Gate.'); - core.setOutput('ci_running', 'false'); } - name: Pass (CI skipped)