diff --git a/.github/workflows/ab-testing-advisor.lock.yml b/.github/workflows/ab-testing-advisor.lock.yml index a5a64e1bba1..bc14df161dc 100644 --- a/.github/workflows/ab-testing-advisor.lock.yml +++ b/.github/workflows/ab-testing-advisor.lock.yml @@ -1982,6 +1982,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/agent-persona-explorer.lock.yml b/.github/workflows/agent-persona-explorer.lock.yml index 75d7a9f1d63..3e092b1d946 100644 --- a/.github/workflows/agent-persona-explorer.lock.yml +++ b/.github/workflows/agent-persona-explorer.lock.yml @@ -2126,6 +2126,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/api-consumption-report.lock.yml b/.github/workflows/api-consumption-report.lock.yml index d4aee2dd073..b5c7ed52f3b 100644 --- a/.github/workflows/api-consumption-report.lock.yml +++ b/.github/workflows/api-consumption-report.lock.yml @@ -2419,6 +2419,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 94e911b93b5..ce7f5caf960 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -2440,6 +2440,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml b/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml index fded1f61cca..7966eddfafe 100644 --- a/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml +++ b/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml @@ -1680,6 +1680,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index 1e7e1ac4188..13a0b4507d9 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -2188,6 +2188,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index aa1d7dff651..f7b30f2ad68 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -2311,6 +2311,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/claude-code-user-docs-review.lock.yml b/.github/workflows/claude-code-user-docs-review.lock.yml index d8d6fcaddbc..1cf7eafb796 100644 --- a/.github/workflows/claude-code-user-docs-review.lock.yml +++ b/.github/workflows/claude-code-user-docs-review.lock.yml @@ -2058,6 +2058,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 5751de92968..6d1e438af6e 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -2042,6 +2042,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index a4c0b28e398..e36f0d4776b 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -2159,6 +2159,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/code-scanning-fixer.lock.yml b/.github/workflows/code-scanning-fixer.lock.yml index 22e7ec5ec8a..7c01c2e614a 100644 --- a/.github/workflows/code-scanning-fixer.lock.yml +++ b/.github/workflows/code-scanning-fixer.lock.yml @@ -2110,6 +2110,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/constraint-solving-potd.lock.yml b/.github/workflows/constraint-solving-potd.lock.yml index ad3cad2d805..b26b1025c76 100644 --- a/.github/workflows/constraint-solving-potd.lock.yml +++ b/.github/workflows/constraint-solving-potd.lock.yml @@ -1649,6 +1649,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 98bea9be3b5..0478b873f83 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -2310,6 +2310,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/copilot-opt.lock.yml b/.github/workflows/copilot-opt.lock.yml index d66076ff60a..9b8065e5e0d 100644 --- a/.github/workflows/copilot-opt.lock.yml +++ b/.github/workflows/copilot-opt.lock.yml @@ -2042,6 +2042,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/copilot-pr-merged-report.lock.yml b/.github/workflows/copilot-pr-merged-report.lock.yml index f9b6eb53fab..a3a8919cc91 100644 --- a/.github/workflows/copilot-pr-merged-report.lock.yml +++ b/.github/workflows/copilot-pr-merged-report.lock.yml @@ -1906,6 +1906,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index ab77576a0ab..5de1a7079e3 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -2189,6 +2189,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 9ed127e7ab7..55c3b81da1e 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -2128,6 +2128,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 18ca05206e3..3f4c4bb5b6a 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -2244,6 +2244,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-architecture-diagram.lock.yml b/.github/workflows/daily-architecture-diagram.lock.yml index 9bd705724c5..2c7b28e015a 100644 --- a/.github/workflows/daily-architecture-diagram.lock.yml +++ b/.github/workflows/daily-architecture-diagram.lock.yml @@ -2223,6 +2223,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-aw-cross-repo-compile-check.lock.yml b/.github/workflows/daily-aw-cross-repo-compile-check.lock.yml index 77ff8c93b16..3424552379b 100644 --- a/.github/workflows/daily-aw-cross-repo-compile-check.lock.yml +++ b/.github/workflows/daily-aw-cross-repo-compile-check.lock.yml @@ -2047,6 +2047,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-cache-strategy-analyzer.lock.yml b/.github/workflows/daily-cache-strategy-analyzer.lock.yml index 37f1f5e1cd4..7857e8038f1 100644 --- a/.github/workflows/daily-cache-strategy-analyzer.lock.yml +++ b/.github/workflows/daily-cache-strategy-analyzer.lock.yml @@ -2349,6 +2349,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-caveman-optimizer.lock.yml b/.github/workflows/daily-caveman-optimizer.lock.yml index 96c11f91520..f98b2bcd648 100644 --- a/.github/workflows/daily-caveman-optimizer.lock.yml +++ b/.github/workflows/daily-caveman-optimizer.lock.yml @@ -2212,6 +2212,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-choice-test.lock.yml b/.github/workflows/daily-choice-test.lock.yml index ebeefa79038..92dba7fdc5a 100644 --- a/.github/workflows/daily-choice-test.lock.yml +++ b/.github/workflows/daily-choice-test.lock.yml @@ -1083,7 +1083,8 @@ jobs: needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim - permissions: {} + permissions: + actions: write concurrency: group: "gh-aw-conclusion-daily-choice-test" cancel-in-progress: false diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index 1f9307141e9..c750ab80fc5 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -2360,6 +2360,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-compiler-quality.lock.yml b/.github/workflows/daily-compiler-quality.lock.yml index 95c7016774c..20a35744b84 100644 --- a/.github/workflows/daily-compiler-quality.lock.yml +++ b/.github/workflows/daily-compiler-quality.lock.yml @@ -2183,6 +2183,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-doc-healer.lock.yml b/.github/workflows/daily-doc-healer.lock.yml index 080618841d7..4d8cfe75858 100644 --- a/.github/workflows/daily-doc-healer.lock.yml +++ b/.github/workflows/daily-doc-healer.lock.yml @@ -2324,6 +2324,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 22b37359bf1..1f549af7ce8 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -2101,6 +2101,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-experiment-report.lock.yml b/.github/workflows/daily-experiment-report.lock.yml index 93d66d055fc..11a72e9d1aa 100644 --- a/.github/workflows/daily-experiment-report.lock.yml +++ b/.github/workflows/daily-experiment-report.lock.yml @@ -2082,6 +2082,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index 3427b9b1673..0d098f7dd99 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -2358,6 +2358,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index f93e2b944cd..a7167b4ecd5 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -1116,6 +1116,7 @@ jobs: needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim permissions: + actions: write contents: read concurrency: group: "gh-aw-conclusion-daily-firewall-report" diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index 028b957e4e1..d641a7dbc32 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -1866,6 +1866,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-function-namer.lock.yml b/.github/workflows/daily-function-namer.lock.yml index 918a2283d04..0c08f75f645 100644 --- a/.github/workflows/daily-function-namer.lock.yml +++ b/.github/workflows/daily-function-namer.lock.yml @@ -1990,6 +1990,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-hippo-learn.lock.yml b/.github/workflows/daily-hippo-learn.lock.yml index 3230e9f17c2..31e55732bbf 100644 --- a/.github/workflows/daily-hippo-learn.lock.yml +++ b/.github/workflows/daily-hippo-learn.lock.yml @@ -2086,6 +2086,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index fcb898dcec3..59e97b42118 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -2350,6 +2350,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-mcp-concurrency-analysis.lock.yml b/.github/workflows/daily-mcp-concurrency-analysis.lock.yml index cc05e5f07e8..566cfd27d6e 100644 --- a/.github/workflows/daily-mcp-concurrency-analysis.lock.yml +++ b/.github/workflows/daily-mcp-concurrency-analysis.lock.yml @@ -2108,6 +2108,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 03cae4af19c..0103ef7892d 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -2382,6 +2382,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index 7cb33a125c1..341b9e74d71 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -2555,6 +2555,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-rendering-scripts-verifier.lock.yml b/.github/workflows/daily-rendering-scripts-verifier.lock.yml index 93702b73904..c8abd5a789e 100644 --- a/.github/workflows/daily-rendering-scripts-verifier.lock.yml +++ b/.github/workflows/daily-rendering-scripts-verifier.lock.yml @@ -2025,6 +2025,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index 44afbea24a8..33256e731d7 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -2037,6 +2037,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-safe-output-optimizer.lock.yml b/.github/workflows/daily-safe-output-optimizer.lock.yml index 04e0997f6c2..afcf94f4c00 100644 --- a/.github/workflows/daily-safe-output-optimizer.lock.yml +++ b/.github/workflows/daily-safe-output-optimizer.lock.yml @@ -2411,6 +2411,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-security-observability.lock.yml b/.github/workflows/daily-security-observability.lock.yml index c083464d74a..8694a50104b 100644 --- a/.github/workflows/daily-security-observability.lock.yml +++ b/.github/workflows/daily-security-observability.lock.yml @@ -2190,6 +2190,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-security-red-team.lock.yml b/.github/workflows/daily-security-red-team.lock.yml index bbe1102dffc..407c4c481b6 100644 --- a/.github/workflows/daily-security-red-team.lock.yml +++ b/.github/workflows/daily-security-red-team.lock.yml @@ -2246,6 +2246,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/daily-spdd-spec-planner.lock.yml b/.github/workflows/daily-spdd-spec-planner.lock.yml index 06bc12e77e8..2cd92953332 100644 --- a/.github/workflows/daily-spdd-spec-planner.lock.yml +++ b/.github/workflows/daily-spdd-spec-planner.lock.yml @@ -1759,6 +1759,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml index da166cecfa6..48156748e3a 100644 --- a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml +++ b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml @@ -2254,6 +2254,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/dead-code-remover.lock.yml b/.github/workflows/dead-code-remover.lock.yml index 5036d8eedd1..9e5903fa5f2 100644 --- a/.github/workflows/dead-code-remover.lock.yml +++ b/.github/workflows/dead-code-remover.lock.yml @@ -1841,6 +1841,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 7b6a7ee0b7f..f8a7248bc4b 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -2425,6 +2425,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/detection-analysis-report.lock.yml b/.github/workflows/detection-analysis-report.lock.yml index f96e57d0497..b51a78932bc 100644 --- a/.github/workflows/detection-analysis-report.lock.yml +++ b/.github/workflows/detection-analysis-report.lock.yml @@ -1883,6 +1883,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 2aa933e160d..ffbfd7f1428 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -2058,6 +2058,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/eslint-miner.lock.yml b/.github/workflows/eslint-miner.lock.yml index 1dedb51b7ac..e4707761ad7 100644 --- a/.github/workflows/eslint-miner.lock.yml +++ b/.github/workflows/eslint-miner.lock.yml @@ -1730,6 +1730,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/firewall-escape.lock.yml b/.github/workflows/firewall-escape.lock.yml index d3a85980e87..201ebfbd0da 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -1962,6 +1962,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/github-mcp-structural-analysis.lock.yml b/.github/workflows/github-mcp-structural-analysis.lock.yml index f98c7c21ced..235a8f74f34 100644 --- a/.github/workflows/github-mcp-structural-analysis.lock.yml +++ b/.github/workflows/github-mcp-structural-analysis.lock.yml @@ -1792,6 +1792,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index 4560892d091..10cfcff2dd1 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -1851,6 +1851,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 5546ebff5db..041169b3066 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -2248,6 +2248,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml index e343e1688e1..a9a4861e382 100644 --- a/.github/workflows/go-fan.lock.yml +++ b/.github/workflows/go-fan.lock.yml @@ -1859,6 +1859,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 3301441938b..3fd12aabef6 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -1868,6 +1868,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/gpclean.lock.yml b/.github/workflows/gpclean.lock.yml index c40a9d38422..7a29b07eb4f 100644 --- a/.github/workflows/gpclean.lock.yml +++ b/.github/workflows/gpclean.lock.yml @@ -1843,6 +1843,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 0ec591c2b8d..08fa1908623 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -1844,6 +1844,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index a86276ddb6a..91b4c28a913 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -1778,6 +1778,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/linter-miner.lock.yml b/.github/workflows/linter-miner.lock.yml index a196afc0a54..831fd737e44 100644 --- a/.github/workflows/linter-miner.lock.yml +++ b/.github/workflows/linter-miner.lock.yml @@ -1806,6 +1806,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 92f9321a622..f937d7bc3e8 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -1766,6 +1766,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index e157e3565d7..2c7ed49f02f 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -2525,6 +2525,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index 8c0d0fbcc8c..d9d5f5c3c91 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -1040,7 +1040,8 @@ jobs: needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim - permissions: {} + permissions: + actions: write concurrency: group: "gh-aw-conclusion-notion-issue-summary" cancel-in-progress: false diff --git a/.github/workflows/org-health-report.lock.yml b/.github/workflows/org-health-report.lock.yml index 19c6e0d171f..b7686af0a99 100644 --- a/.github/workflows/org-health-report.lock.yml +++ b/.github/workflows/org-health-report.lock.yml @@ -1800,6 +1800,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/outcome-collector.lock.yml b/.github/workflows/outcome-collector.lock.yml index aa830d00df8..84573830b32 100644 --- a/.github/workflows/outcome-collector.lock.yml +++ b/.github/workflows/outcome-collector.lock.yml @@ -1732,6 +1732,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 32b4ffb5e0e..4a5683dde26 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -1916,6 +1916,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index c02cab567a0..171ac896aae 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -1408,7 +1408,8 @@ jobs: needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim - permissions: {} + permissions: + actions: write concurrency: group: "gh-aw-conclusion-poem-bot" cancel-in-progress: false @@ -2128,6 +2129,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/portfolio-analyst.lock.yml b/.github/workflows/portfolio-analyst.lock.yml index ac6b3680024..39e7add3432 100644 --- a/.github/workflows/portfolio-analyst.lock.yml +++ b/.github/workflows/portfolio-analyst.lock.yml @@ -1902,6 +1902,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/pr-code-quality-reviewer.lock.yml b/.github/workflows/pr-code-quality-reviewer.lock.yml index 38d7264d73e..22b13a20f2e 100644 --- a/.github/workflows/pr-code-quality-reviewer.lock.yml +++ b/.github/workflows/pr-code-quality-reviewer.lock.yml @@ -2222,6 +2222,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index a678cfb0fcc..75020e88571 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -1911,6 +1911,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index ab55aac5bff..b83ef822300 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -1867,6 +1867,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/refactoring-cadence.lock.yml b/.github/workflows/refactoring-cadence.lock.yml index a5012630619..cf7f2d02ae1 100644 --- a/.github/workflows/refactoring-cadence.lock.yml +++ b/.github/workflows/refactoring-cadence.lock.yml @@ -1786,6 +1786,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/repo-audit-analyzer.lock.yml b/.github/workflows/repo-audit-analyzer.lock.yml index 3d8e2ce3c0d..08e1b9175d8 100644 --- a/.github/workflows/repo-audit-analyzer.lock.yml +++ b/.github/workflows/repo-audit-analyzer.lock.yml @@ -1712,6 +1712,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index 3bd870483e0..cb1b3cbbbf4 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -1718,6 +1718,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 74cdc582641..a8795ed3e4e 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -1884,6 +1884,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 8bcaecf5444..825cbb5b23a 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -1683,6 +1683,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index ccc2e0c8051..3ecb7c7aa95 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -2067,6 +2067,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/slide-deck-maintainer.lock.yml b/.github/workflows/slide-deck-maintainer.lock.yml index d4ab5f46648..7cd6e189dfb 100644 --- a/.github/workflows/slide-deck-maintainer.lock.yml +++ b/.github/workflows/slide-deck-maintainer.lock.yml @@ -1908,6 +1908,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-antigravity.lock.yml b/.github/workflows/smoke-antigravity.lock.yml index 1af6c55d0cc..74e2a0261b8 100644 --- a/.github/workflows/smoke-antigravity.lock.yml +++ b/.github/workflows/smoke-antigravity.lock.yml @@ -1986,6 +1986,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-call-workflow.lock.yml b/.github/workflows/smoke-call-workflow.lock.yml index ef2fac94565..4b76ca1f608 100644 --- a/.github/workflows/smoke-call-workflow.lock.yml +++ b/.github/workflows/smoke-call-workflow.lock.yml @@ -1112,7 +1112,7 @@ jobs: # Imported from called workflow "smoke-workflow-call" because GitHub requires the caller job to grant permissions requested by reusable workflow jobs. # Review the called workflow's job-level permissions in ./.github/workflows/smoke-workflow-call.lock.yml. permissions: - actions: read + actions: write contents: read issues: write pull-requests: write @@ -1142,7 +1142,8 @@ jobs: needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim - permissions: {} + permissions: + actions: write concurrency: group: "gh-aw-conclusion-smoke-call-workflow" cancel-in-progress: false diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index d87ab193b7a..47ca4ae4594 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -2798,6 +2798,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 14b0b1e3b68..2d357bec782 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -2541,6 +2541,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml index 731a7015c06..b0eb8704f90 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml @@ -3104,6 +3104,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-copilot-aoai-entra.lock.yml b/.github/workflows/smoke-copilot-aoai-entra.lock.yml index d984cd8bf7f..6c578bab75e 100644 --- a/.github/workflows/smoke-copilot-aoai-entra.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-entra.lock.yml @@ -3116,6 +3116,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index b355829b508..6afdfbbeca6 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -2826,6 +2826,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 8837522f51d..71c3cce8199 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -3411,6 +3411,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index a2ff96a5743..29122286ef7 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -2061,6 +2061,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-pi.lock.yml b/.github/workflows/smoke-pi.lock.yml index 5d9fb4fc892..f6ff0072172 100644 --- a/.github/workflows/smoke-pi.lock.yml +++ b/.github/workflows/smoke-pi.lock.yml @@ -1877,6 +1877,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 45eebc2d2e5..a15928ed932 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -2008,6 +2008,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/spec-enforcer.lock.yml b/.github/workflows/spec-enforcer.lock.yml index df781cbfe7e..cd04d0e515a 100644 --- a/.github/workflows/spec-enforcer.lock.yml +++ b/.github/workflows/spec-enforcer.lock.yml @@ -2006,6 +2006,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/spec-extractor.lock.yml b/.github/workflows/spec-extractor.lock.yml index d46c626dd13..98641214189 100644 --- a/.github/workflows/spec-extractor.lock.yml +++ b/.github/workflows/spec-extractor.lock.yml @@ -1839,6 +1839,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index 34e841da12d..0ecaf91f678 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -1293,7 +1293,8 @@ jobs: needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim - permissions: {} + permissions: + actions: write concurrency: group: "gh-aw-conclusion-stale-repo-identifier-${{ inputs.organization || github.run_id }}" cancel-in-progress: false @@ -2222,6 +2223,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index 89a09589ef1..e6cacd1a4c1 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -1916,6 +1916,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/step-name-alignment.lock.yml b/.github/workflows/step-name-alignment.lock.yml index b87f007757e..750e47db247 100644 --- a/.github/workflows/step-name-alignment.lock.yml +++ b/.github/workflows/step-name-alignment.lock.yml @@ -1798,6 +1798,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index 6252f735ccf..d1feb96c23d 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -1796,6 +1796,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index ce2955568d0..e4a38432b88 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -2258,6 +2258,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 24be2a95f21..01bb89d1601 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -2185,6 +2185,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 3423d43122b..9fbaf7a87fe 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -1757,6 +1757,7 @@ jobs: if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: + actions: write contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} diff --git a/docs/adr/47046-add-actions-write-permission-to-cache-save-jobs.md b/docs/adr/47046-add-actions-write-permission-to-cache-save-jobs.md new file mode 100644 index 00000000000..203bd86295b --- /dev/null +++ b/docs/adr/47046-add-actions-write-permission-to-cache-save-jobs.md @@ -0,0 +1,48 @@ +# ADR-47046: Add `actions: write` Permission to Cache-Save Jobs + +**Date**: 2026-07-21 +**Status**: Draft +**Deciders**: Unknown (Copilot SWE Agent, pelikhan) + +--- + +### Context + +GitHub's cache-reservation backend (`actions/cache`) requires the calling token to have at least one writable scope before it will accept a cache-save operation. The `update_cache_memory` job (in `pkg/workflow/cache.go`) and the `conclusion` job (in `pkg/workflow/notify_comment.go`) were generated with `permissions: {}` (empty, i.e. read-only), which caused every cache-save step to fail with `cache write denied: token has no writable scopes`. Because all cache steps carry `continue-on-error: true`, these failures were silent: runs stayed green while `cache-memory` and daily-AIC lineage were never persisted. The fix must grant the minimum additional scope required by the GitHub API without broadening the attack surface of these jobs. + +### Decision + +We decided to replace `NewPermissionsEmpty()` with `NewPermissionsActionsWrite()` for the `update_cache_memory` job, and to conditionally add `actions: write` to the `conclusionPerms` block when `hasMaxDailyAICGuardrail && WorkflowID != ""` (i.e., exactly when `buildDailyAICUsageCacheSteps` injects an `actions/cache/save` step). In dev mode (local action checkout), `contents: read` is additionally set on the `update_cache_memory` job because the action checkout requires repository access. This grants the minimum privilege required by GitHub's cache backend while leaving all other permission surfaces unchanged. + +### Alternatives Considered + +#### Alternative 1: Keep Empty Permissions and Use a Repository-Level Token + +Rely on a separately injected PAT or app token that already has `actions: write` scope, rather than elevating the GITHUB_TOKEN scope on these jobs. This avoids any permission change to generated lock files. + +Rejected because: it introduces an external secret dependency and a secret-rotation burden for a routine cache operation. The GITHUB_TOKEN with `actions: write` is the idiomatic GitHub Actions approach for cache saves and does not require any additional secrets infrastructure. + +#### Alternative 2: Grant `contents: write` Instead of `actions: write` + +Elevate to `contents: write`, which also satisfies the cache backend's "needs a writable scope" check. + +Rejected because: `contents: write` grants write access to repository contents, which is far beyond what a cache-save step needs. `actions: write` is the narrowest scope that satisfies the requirement and follows the least-privilege principle documented in GitHub's Actions security hardening guide. + +### Consequences + +#### Positive +- Cache saves for `update_cache_memory` and daily-AIC `conclusion` jobs now succeed, so `cache-memory` state and daily-AIC lineage are properly persisted across runs. +- The fix uses the minimum necessary privilege (`actions: write` only), preserving the least-privilege posture of all affected jobs. +- A conditional logic path ensures that `actions: write` is added to `conclusionPerms` only when the cache-save step is actually injected, preventing unnecessary permission grants in other conclusion job variants. + +#### Negative +- All 260 compiled lock files required regeneration, producing a large diff that makes the core logic change harder to review at first glance. +- Any future refactor of `NewPermissionsEmpty()` usage must audit whether the call site performs cache writes, to avoid reintroducing silent failures. + +#### Neutral +- The dev-mode code path (`setupActionRef != "" && len(c.generateCheckoutActionsFolder(data)) > 0`) additionally receives `contents: read` on the `update_cache_memory` job; this is consistent with existing dev-mode checkout requirements and does not affect production workflows. +- The `continue-on-error: true` pattern on cache steps remains unchanged; the fix resolves the root cause rather than removing the error-suppression mechanism. + +--- + +*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* diff --git a/pkg/workflow/cache.go b/pkg/workflow/cache.go index 18689f0ec67..1fc9e9d00ed 100644 --- a/pkg/workflow/cache.go +++ b/pkg/workflow/cache.go @@ -919,6 +919,7 @@ func (c *Compiler) buildUpdateCacheMemoryJob(data *WorkflowData, threatDetection cacheLog.Printf("Building update_cache_memory job for %d caches (threatDetectionEnabled=%v)", len(data.CacheMemoryConfig.Caches), threatDetectionEnabled) var steps []string + hasCacheSaveStep := false // Build steps for each cache // In workflow_call context, use the per-invocation prefix from the agent job. @@ -1013,6 +1014,7 @@ func (c *Compiler) buildUpdateCacheMemoryJob(data *WorkflowData, threatDetection fmt.Fprintf(&saveStep, " key: %s\n", cacheKey) fmt.Fprintf(&saveStep, " path: %s\n", cacheDir) steps = append(steps, saveStep.String()) + hasCacheSaveStep = true } // If no writable caches, return nil @@ -1048,14 +1050,18 @@ func (c *Compiler) buildUpdateCacheMemoryJob(data *WorkflowData, threatDetection ) jobCondition := RenderCondition(BuildAnd(BuildAnd(BuildFunctionCall("always"), buildDetectionSuccessCondition()), agentSucceeded)) - // Set up permissions for the cache update job - // If using local actions (dev mode without action-tag), we need contents: read to checkout the actions folder - permissions := NewPermissionsEmpty().RenderToYAML() // Default: no special permissions needed + // Set up permissions for the cache update job. + // actions: write is required only when this job actually emits cache-save steps. + // Without it, cache saves fail with "cache write denied: token has no writable scopes". + perms := NewPermissionsEmpty() + if hasCacheSaveStep { + perms.Set(PermissionActions, PermissionWrite) + } if setupActionRef != "" && len(c.generateCheckoutActionsFolder(data)) > 0 { - // Need contents: read to checkout the actions folder - perms := NewPermissionsContentsRead() - permissions = perms.RenderToYAML() + // In dev mode (local action path), also need contents: read to checkout the actions folder + perms.Set(PermissionContents, PermissionRead) } + permissions := perms.RenderToYAML() // Set GH_AW_WORKFLOW_ID_SANITIZED so cache keys match those used in the agent job var jobEnv map[string]string diff --git a/pkg/workflow/cache_memory_threat_detection_test.go b/pkg/workflow/cache_memory_threat_detection_test.go index e1a7711a34e..d590bb8cbd8 100644 --- a/pkg/workflow/cache_memory_threat_detection_test.go +++ b/pkg/workflow/cache_memory_threat_detection_test.go @@ -8,6 +8,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/goccy/go-yaml" ) // TestCacheMemoryWithThreatDetection verifies that when threat detection is enabled, @@ -15,10 +17,11 @@ import ( // an update_cache_memory job to save the cache after detection succeeds func TestCacheMemoryWithThreatDetection(t *testing.T) { tests := []struct { - name string - frontmatter string - expectedInLock []string - notExpectedInLock []string + name string + frontmatter string + expectedInLock []string + notExpectedInLock []string + expectUpdateCacheMemoryActionsWrite bool }{ { name: "cache-memory with threat detection enabled", @@ -66,6 +69,7 @@ Test workflow with cache-memory and threat detection enabled.`, // Should NOT use regular actions/cache in agent job "- name: Restore cache-memory file share data\n uses: actions/cache@", }, + expectUpdateCacheMemoryActionsWrite: true, }, { name: "cache-memory without threat detection", @@ -242,6 +246,44 @@ Test workflow with restore-only cache-memory and threat detection enabled.`, t.Errorf("Expected lock YAML NOT to contain %q, but it did.\nContext around match (lines %d-%d):\n%s", notExpected, start+1, end, context) } } + + if tt.expectUpdateCacheMemoryActionsWrite { + actionsPermission := extractJobPermission(t, lockContent, "update_cache_memory", "actions") + if actionsPermission != "write" { + t.Errorf("Expected update_cache_memory job permissions.actions = %q, got %q", "write", actionsPermission) + } + } }) } } + +func extractJobPermission(t *testing.T, lockContent, jobName, permissionName string) string { + t.Helper() + + var workflow map[string]any + if err := yaml.Unmarshal([]byte(lockContent), &workflow); err != nil { + t.Fatalf("Failed to parse lock YAML: %v", err) + } + + jobs, ok := workflow["jobs"].(map[string]any) + if !ok { + t.Fatal("Expected compiled workflow to contain a jobs map") + } + + job, ok := jobs[jobName].(map[string]any) + if !ok { + t.Fatalf("Expected compiled workflow to contain job %q", jobName) + } + + permissions, ok := job["permissions"].(map[string]any) + if !ok { + t.Fatalf("Expected job %q to contain a permissions map", jobName) + } + + value, ok := permissions[permissionName].(string) + if !ok { + t.Fatalf("Expected job %q permission %q to be a string", jobName, permissionName) + } + + return value +} diff --git a/pkg/workflow/compiler_jobs_test.go b/pkg/workflow/compiler_jobs_test.go index 24540f7585b..d1a7b89612a 100644 --- a/pkg/workflow/compiler_jobs_test.go +++ b/pkg/workflow/compiler_jobs_test.go @@ -2327,6 +2327,130 @@ func TestUpdateCacheMemoryJobConditionRequiresAgentSuccess(t *testing.T) { } } +// TestUpdateCacheMemoryJobHasActionsWritePermission verifies that the update_cache_memory job +// has actions: write permission so GitHub's cache-reservation backend allows cache saves. +// Without this permission, cache saves fail with "token has no writable scopes". +func TestUpdateCacheMemoryJobHasActionsWritePermission(t *testing.T) { + compiler := NewCompiler() + compiler.jobManager = NewJobManager() + + data := &WorkflowData{ + Name: "Test Workflow", + AI: "copilot", + RunsOn: "runs-on: ubuntu-latest", + CacheMemoryConfig: &CacheMemoryConfig{ + Caches: []CacheMemoryEntry{ + {ID: "default"}, + }, + }, + SafeOutputs: &SafeOutputsConfig{ + ThreatDetection: &ThreatDetectionConfig{}, + }, + } + + compiler.stepOrderTracker = NewStepOrderTracker() + activationJob, _ := compiler.buildActivationJob(data, false, "", "test.lock.yml") + compiler.jobManager.AddJob(activationJob) + + agentJob, _ := compiler.buildMainJob(data, true) + compiler.jobManager.AddJob(agentJob) + + compiler.buildSafeOutputsJobs(data, string(constants.AgentJobName), "test.md") + + updateCacheMemoryJob, err := compiler.buildUpdateCacheMemoryJob(data, true) + if err != nil { + t.Fatalf("buildUpdateCacheMemoryJob() error: %v", err) + } + if updateCacheMemoryJob == nil { + t.Fatal("Expected update_cache_memory job to be created") + } + + // Must have actions: write so cache saves are not rejected with "token has no writable scopes". + if !strings.Contains(updateCacheMemoryJob.Permissions, "actions: write") { + t.Errorf("update_cache_memory job must have 'actions: write' permission for cache saves, got: %q", updateCacheMemoryJob.Permissions) + } +} + +func TestUpdateCacheMemoryJobHasActionsWritePermissionInDevMode(t *testing.T) { + compiler := NewCompiler(WithVersion("dev")) + compiler.SetActionMode(ActionModeDev) + compiler.jobManager = NewJobManager() + + data := &WorkflowData{ + Name: "Test Workflow", + AI: "copilot", + RunsOn: "runs-on: ubuntu-latest", + CacheMemoryConfig: &CacheMemoryConfig{ + Caches: []CacheMemoryEntry{ + {ID: "default"}, + }, + }, + SafeOutputs: &SafeOutputsConfig{ + ThreatDetection: &ThreatDetectionConfig{}, + }, + } + + compiler.stepOrderTracker = NewStepOrderTracker() + activationJob, _ := compiler.buildActivationJob(data, false, "", "test.lock.yml") + compiler.jobManager.AddJob(activationJob) + + agentJob, _ := compiler.buildMainJob(data, true) + compiler.jobManager.AddJob(agentJob) + + compiler.buildSafeOutputsJobs(data, string(constants.AgentJobName), "test.md") + + updateCacheMemoryJob, err := compiler.buildUpdateCacheMemoryJob(data, true) + if err != nil { + t.Fatalf("buildUpdateCacheMemoryJob() error: %v", err) + } + if updateCacheMemoryJob == nil { + t.Fatal("Expected update_cache_memory job to be created") + } + + if !strings.Contains(updateCacheMemoryJob.Permissions, "actions: write") { + t.Errorf("dev-mode update_cache_memory job must preserve 'actions: write', got: %q", updateCacheMemoryJob.Permissions) + } + if !strings.Contains(updateCacheMemoryJob.Permissions, "contents: read") { + t.Errorf("dev-mode update_cache_memory job must include 'contents: read', got: %q", updateCacheMemoryJob.Permissions) + } +} + +func TestUpdateCacheMemoryJobSkippedForRestoreOnlyCaches(t *testing.T) { + compiler := NewCompiler() + compiler.jobManager = NewJobManager() + + data := &WorkflowData{ + Name: "Test Workflow", + AI: "copilot", + RunsOn: "runs-on: ubuntu-latest", + CacheMemoryConfig: &CacheMemoryConfig{ + Caches: []CacheMemoryEntry{ + {ID: "default", RestoreOnly: true}, + }, + }, + SafeOutputs: &SafeOutputsConfig{ + ThreatDetection: &ThreatDetectionConfig{}, + }, + } + + compiler.stepOrderTracker = NewStepOrderTracker() + activationJob, _ := compiler.buildActivationJob(data, false, "", "test.lock.yml") + compiler.jobManager.AddJob(activationJob) + + agentJob, _ := compiler.buildMainJob(data, true) + compiler.jobManager.AddJob(agentJob) + + compiler.buildSafeOutputsJobs(data, string(constants.AgentJobName), "test.md") + + updateCacheMemoryJob, err := compiler.buildUpdateCacheMemoryJob(data, true) + if err != nil { + t.Fatalf("buildUpdateCacheMemoryJob() error: %v", err) + } + if updateCacheMemoryJob != nil { + t.Fatalf("Expected update_cache_memory job to be omitted for restore-only caches, got permissions: %q", updateCacheMemoryJob.Permissions) + } +} + // ======================================== // Edge Case Tests // ======================================== diff --git a/pkg/workflow/notify_comment.go b/pkg/workflow/notify_comment.go index b82cb64e272..0ef8b75cdb2 100644 --- a/pkg/workflow/notify_comment.go +++ b/pkg/workflow/notify_comment.go @@ -81,6 +81,12 @@ func (c *Compiler) buildConclusionJob(data *WorkflowData, mainJobName string, sa if hasOTLPGitHubOIDCAuth(data.ParsedFrontmatter, data.RawFrontmatter) { conclusionPerms.Set(PermissionIdToken, PermissionWrite) } + // The daily-AIC usage cache save step must not run with a fully read-only GITHUB_TOKEN. + // If safe-outputs already granted some writable scope (for example issues: write for + // comment updates), reuse that existing write access instead of broadening the job. + if needsDailyAICCachePermission(data) && !conclusionPerms.HasAnyWriteScope() { + conclusionPerms.Set(PermissionActions, PermissionWrite) + } return &Job{ Name: "conclusion", If: RenderCondition(buildConclusionJobCondition(data, mainJobName, safeOutputJobNames)), diff --git a/pkg/workflow/notify_comment_conclusion_helpers.go b/pkg/workflow/notify_comment_conclusion_helpers.go index 54caa79b240..bdc7bb8a1af 100644 --- a/pkg/workflow/notify_comment_conclusion_helpers.go +++ b/pkg/workflow/notify_comment_conclusion_helpers.go @@ -11,6 +11,10 @@ import ( "github.com/github/gh-aw/pkg/workflow/compilerenv" ) +func needsDailyAICCachePermission(data *WorkflowData) bool { + return hasMaxDailyAICGuardrail(data) && data.WorkflowID != "" +} + // buildConclusionSetupSteps extracts the common setup, token minting, and artifact steps. func (c *Compiler) buildConclusionSetupSteps(data *WorkflowData) []string { var steps []string @@ -47,7 +51,7 @@ func (c *Compiler) buildConclusionSetupSteps(data *WorkflowData) []string { // In workflow_call context, use the per-invocation prefix to avoid artifact name clashes. steps = append(steps, buildAgentOutputDownloadSteps(artifactPrefixExprForDownstreamJob(data), c.getActionPin)...) steps = append(steps, buildUsageArtifactUploadSteps(artifactPrefixExprForDownstreamJob(data), data.Evals != nil && data.Evals.HasEvals(), c.getActionPin)...) - if hasMaxDailyAICGuardrail(data) && data.WorkflowID != "" { + if needsDailyAICCachePermission(data) { steps = append(steps, buildDailyAICUsageCacheSteps(data, c.getActionPin)...) } diff --git a/pkg/workflow/notify_comment_test.go b/pkg/workflow/notify_comment_test.go index 6e08bd4d686..cad30ac7567 100644 --- a/pkg/workflow/notify_comment_test.go +++ b/pkg/workflow/notify_comment_test.go @@ -1510,3 +1510,99 @@ func TestConclusionJobNeedsPreActivationFromMessages(t *testing.T) { }) } } + +// TestConclusionJobActionsWritePermissionForDailyAICCache verifies that the conclusion job +// adds actions: write only when daily-AIC cache steps are included and the job would +// otherwise have no writable scope. Existing writable scopes (for example issues: write +// from add-comments) should be reused instead of broadening permissions. +func TestConclusionJobActionsWritePermissionForDailyAICCache(t *testing.T) { + compiler := NewCompiler() + + t.Run("has actions: write when WorkflowID set and no other writable scope exists", func(t *testing.T) { + workflowData := &WorkflowData{ + Name: "Test Workflow", + WorkflowID: "my-workflow", + SafeOutputs: &SafeOutputsConfig{}, + } + job, err := compiler.buildConclusionJob(workflowData, string(constants.AgentJobName), []string{}) + if err != nil { + t.Fatalf("buildConclusionJob returned error: %v", err) + } + if job == nil { + t.Fatal("Expected conclusion job to be non-nil") + } + if !strings.Contains(job.Permissions, "actions: write") { + t.Errorf("conclusion job must have 'actions: write' when daily-AIC cache is active, got: %q", job.Permissions) + } + }) + + t.Run("no actions: write when another writable scope already exists", func(t *testing.T) { + workflowData := &WorkflowData{ + Name: "Test Workflow", + WorkflowID: "my-workflow", + SafeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, + }, + }, + } + job, err := compiler.buildConclusionJob(workflowData, string(constants.AgentJobName), []string{}) + if err != nil { + t.Fatalf("buildConclusionJob returned error: %v", err) + } + if job == nil { + t.Fatal("Expected conclusion job to be non-nil") + } + if strings.Contains(job.Permissions, "actions: write") { + t.Errorf("conclusion job should reuse existing writable scopes instead of adding 'actions: write', got: %q", job.Permissions) + } + if !strings.Contains(job.Permissions, "issues: write") { + t.Errorf("conclusion job should preserve existing 'issues: write' permission, got: %q", job.Permissions) + } + }) + + t.Run("no actions: write when WorkflowID is empty", func(t *testing.T) { + workflowData := &WorkflowData{ + Name: "Test Workflow", + WorkflowID: "", // no WorkflowID → no daily-AIC cache steps + SafeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, + }, + }, + } + job, err := compiler.buildConclusionJob(workflowData, string(constants.AgentJobName), []string{}) + if err != nil { + t.Fatalf("buildConclusionJob returned error: %v", err) + } + if job == nil { + t.Fatal("Expected conclusion job to be non-nil") + } + if strings.Contains(job.Permissions, "actions: write") { + t.Errorf("conclusion job should NOT have 'actions: write' when WorkflowID is empty, got: %q", job.Permissions) + } + }) + + t.Run("no actions: write when guardrail is explicitly disabled", func(t *testing.T) { + workflowData := &WorkflowData{ + Name: "Test Workflow", + WorkflowID: "my-workflow", + RawFrontmatter: disableAICGuardrailFrontmatter(), + SafeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, + }, + }, + } + job, err := compiler.buildConclusionJob(workflowData, string(constants.AgentJobName), []string{}) + if err != nil { + t.Fatalf("buildConclusionJob returned error: %v", err) + } + if job == nil { + t.Fatal("Expected conclusion job to be non-nil") + } + if strings.Contains(job.Permissions, "actions: write") { + t.Errorf("conclusion job should NOT have 'actions: write' when the daily-AIC guardrail is disabled, got: %q", job.Permissions) + } + }) +} diff --git a/pkg/workflow/permissions_operations.go b/pkg/workflow/permissions_operations.go index f05cc49bc74..bf96bb08380 100644 --- a/pkg/workflow/permissions_operations.go +++ b/pkg/workflow/permissions_operations.go @@ -66,6 +66,32 @@ func (p *Permissions) HasCopilotRequestsWrite() bool { return ok && level == PermissionWrite } +// HasAnyWriteScope returns true if the permissions grant write access to any +// GITHUB_TOKEN scope. This is useful for jobs that only need to avoid an +// otherwise read-only token, regardless of which specific writable scope +// provides that property. +func (p *Permissions) HasAnyWriteScope() bool { + if p == nil { + return false + } + + if p.shorthand == "write-all" { + return true + } + + if p.hasAll && p.allLevel == PermissionWrite { + return true + } + + for _, level := range p.permissions { + if level == PermissionWrite { + return true + } + } + + return false +} + // hasCopilotRequestsWritePermission returns true when workflow permissions include // copilot-requests: write. This controls whether engines should use ${{ github.token }} // for Copilot authentication instead of requiring COPILOT_GITHUB_TOKEN.