From a9de0a0874793bb20c76e8c8f29c3df26496b22d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:18:59 +0000 Subject: [PATCH 1/8] Initial plan From 844878bd2230f2a914f1d7feccd1d320d895e154 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:26:26 +0000 Subject: [PATCH 2/8] Plan: fix checkout manifest template injection handling Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/ab-testing-advisor.lock.yml | 3 ++- .github/workflows/ace-editor.lock.yml | 3 ++- .github/workflows/agent-performance-analyzer.lock.yml | 3 ++- .github/workflows/agent-persona-explorer.lock.yml | 3 ++- .github/workflows/agentic-token-audit.lock.yml | 3 ++- .github/workflows/agentic-token-optimizer.lock.yml | 3 ++- .github/workflows/agentic-token-trend-audit.lock.yml | 3 ++- .github/workflows/architecture-guardian.lock.yml | 3 ++- .github/workflows/aw-failure-investigator.lock.yml | 3 ++- .github/workflows/bot-detection.lock.yml | 3 ++- .github/workflows/breaking-change-checker.lock.yml | 3 ++- .github/workflows/ci-doctor.lock.yml | 3 ++- .github/workflows/cli-consistency-checker.lock.yml | 3 ++- .github/workflows/cli-version-checker.lock.yml | 3 ++- .github/workflows/codex-github-remote-mcp-test.lock.yml | 3 ++- .github/workflows/contribution-check.lock.yml | 3 ++- .github/workflows/copilot-opt.lock.yml | 3 ++- .github/workflows/daily-agentrx-trace-optimizer.lock.yml | 3 ++- .github/workflows/daily-ambient-context-optimizer.lock.yml | 3 ++- .github/workflows/daily-architecture-diagram.lock.yml | 3 ++- .github/workflows/daily-aw-cross-repo-compile-check.lock.yml | 3 ++- .github/workflows/daily-awf-spec-compiler-surfacing.lock.yml | 3 ++- .github/workflows/daily-byok-ollama-test.lock.yml | 3 ++- .github/workflows/daily-cache-strategy-analyzer.lock.yml | 3 ++- .github/workflows/daily-cli-performance.lock.yml | 3 ++- .github/workflows/daily-cli-tools-tester.lock.yml | 3 ++- .github/workflows/daily-community-attribution.lock.yml | 3 ++- .github/workflows/daily-credit-limit-test.lock.yml | 3 ++- .github/workflows/daily-doc-healer.lock.yml | 3 ++- .github/workflows/daily-file-diet.lock.yml | 3 ++- .github/workflows/daily-formal-spec-verifier.lock.yml | 3 ++- .github/workflows/daily-function-namer.lock.yml | 3 ++- .../daily-grafana-otel-instrumentation-advisor.lock.yml | 3 ++- .github/workflows/daily-max-ai-credits-test.lock.yml | 3 ++- .github/workflows/daily-mcp-concurrency-analysis.lock.yml | 3 ++- .github/workflows/daily-model-inventory.lock.yml | 3 ++- .github/workflows/daily-multi-device-docs-tester.lock.yml | 3 ++- .github/workflows/daily-otel-instrumentation-advisor.lock.yml | 3 ++- .github/workflows/daily-reliability-review.lock.yml | 3 ++- .github/workflows/daily-safe-output-optimizer.lock.yml | 3 ++- .github/workflows/daily-safe-outputs-conformance.lock.yml | 3 ++- .github/workflows/daily-safeoutputs-git-simulator.lock.yml | 3 ++- .github/workflows/daily-security-red-team.lock.yml | 3 ++- .github/workflows/daily-skill-optimizer.lock.yml | 3 ++- .github/workflows/daily-spdd-spec-planner.lock.yml | 3 ++- .github/workflows/daily-syntax-error-quality.lock.yml | 3 ++- .github/workflows/daily-team-status.lock.yml | 3 ++- .github/workflows/daily-testify-uber-super-expert.lock.yml | 3 ++- .github/workflows/daily-token-consumption-report.lock.yml | 3 ++- .../daily-windows-terminal-integration-builder.lock.yml | 3 ++- .github/workflows/deep-report.lock.yml | 3 ++- .github/workflows/delight.lock.yml | 3 ++- .github/workflows/dependabot-burner.lock.yml | 3 ++- .github/workflows/dependabot-go-checker.lock.yml | 3 ++- .github/workflows/deployment-incident-monitor.lock.yml | 3 ++- .github/workflows/designer-drift-audit.lock.yml | 3 ++- .github/workflows/dev.lock.yml | 3 ++- .github/workflows/discussion-task-miner.lock.yml | 3 ++- .github/workflows/duplicate-code-detector.lock.yml | 3 ++- .github/workflows/example-permissions-warning.lock.yml | 3 ++- .github/workflows/firewall.lock.yml | 3 ++- .github/workflows/go-fan.lock.yml | 3 ++- .github/workflows/go-pattern-detector.lock.yml | 3 ++- .github/workflows/gpclean.lock.yml | 3 ++- .github/workflows/hippo-embed.lock.yml | 3 ++- .github/workflows/issue-arborist.lock.yml | 3 ++- .github/workflows/lint-monster.lock.yml | 3 ++- .github/workflows/metrics-collector.lock.yml | 3 ++- .github/workflows/objective-impact-report.lock.yml | 3 ++- .github/workflows/otlp-data-quality-validator.lock.yml | 3 ++- .github/workflows/outcome-collector.lock.yml | 3 ++- .github/workflows/plan.lock.yml | 3 ++- .github/workflows/poem-bot.lock.yml | 3 ++- .github/workflows/pr-triage-agent.lock.yml | 3 ++- .github/workflows/refactoring-cadence.lock.yml | 3 ++- .github/workflows/ruflo-backed-task.lock.yml | 3 ++- .github/workflows/security-compliance.lock.yml | 3 ++- .github/workflows/semantic-function-refactor.lock.yml | 3 ++- .github/workflows/sergo.lock.yml | 3 ++- .github/workflows/smoke-antigravity.lock.yml | 3 ++- .github/workflows/smoke-ci.lock.yml | 3 ++- .github/workflows/smoke-claude.lock.yml | 3 ++- .github/workflows/smoke-codex.lock.yml | 3 ++- .github/workflows/smoke-copilot-aoai-apikey.lock.yml | 3 ++- .github/workflows/smoke-copilot-arm.lock.yml | 3 ++- .github/workflows/smoke-copilot-sdk.lock.yml | 3 ++- .github/workflows/smoke-copilot.lock.yml | 3 ++- .github/workflows/smoke-create-cross-repo-pr.lock.yml | 3 ++- .github/workflows/smoke-crush.lock.yml | 3 ++- .github/workflows/smoke-gemini.lock.yml | 3 ++- .github/workflows/smoke-opencode.lock.yml | 3 ++- .github/workflows/smoke-otel-backends.lock.yml | 3 ++- .github/workflows/smoke-pi.lock.yml | 3 ++- .github/workflows/smoke-project.lock.yml | 3 ++- .github/workflows/smoke-temporary-id.lock.yml | 3 ++- .github/workflows/smoke-update-cross-repo-pr.lock.yml | 3 ++- .github/workflows/smoke-workflow-call-with-inputs.lock.yml | 3 ++- .github/workflows/spec-librarian.lock.yml | 3 ++- .github/workflows/stale-repo-identifier.lock.yml | 3 ++- .github/workflows/static-analysis-report.lock.yml | 3 ++- .github/workflows/step-name-alignment.lock.yml | 3 ++- .github/workflows/super-linter.lock.yml | 3 ++- .github/workflows/test-workflow.lock.yml | 3 ++- .github/workflows/video-analyzer.lock.yml | 3 ++- .github/workflows/workflow-health-manager.lock.yml | 3 ++- .github/workflows/workflow-normalizer.lock.yml | 3 ++- .github/workflows/workflow-skill-extractor.lock.yml | 3 ++- 107 files changed, 214 insertions(+), 107 deletions(-) diff --git a/.github/workflows/ab-testing-advisor.lock.yml b/.github/workflows/ab-testing-advisor.lock.yml index ced4e0eb656..a15eb0f0570 100644 --- a/.github/workflows/ab-testing-advisor.lock.yml +++ b/.github/workflows/ab-testing-advisor.lock.yml @@ -552,7 +552,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/ace-editor.lock.yml b/.github/workflows/ace-editor.lock.yml index 9be6736e082..e69d6e4ae5d 100644 --- a/.github/workflows/ace-editor.lock.yml +++ b/.github/workflows/ace-editor.lock.yml @@ -550,7 +550,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index fdefe1c691f..921540ca4f5 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -706,7 +706,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/agent-persona-explorer.lock.yml b/.github/workflows/agent-persona-explorer.lock.yml index 7a9b7259aae..c6d05fb6172 100644 --- a/.github/workflows/agent-persona-explorer.lock.yml +++ b/.github/workflows/agent-persona-explorer.lock.yml @@ -654,7 +654,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/agentic-token-audit.lock.yml b/.github/workflows/agentic-token-audit.lock.yml index 9df3850acf2..7be5747052b 100644 --- a/.github/workflows/agentic-token-audit.lock.yml +++ b/.github/workflows/agentic-token-audit.lock.yml @@ -626,7 +626,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/agentic-token-optimizer.lock.yml b/.github/workflows/agentic-token-optimizer.lock.yml index fae3d7e51e5..6d61db0c2a5 100644 --- a/.github/workflows/agentic-token-optimizer.lock.yml +++ b/.github/workflows/agentic-token-optimizer.lock.yml @@ -549,7 +549,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/agentic-token-trend-audit.lock.yml b/.github/workflows/agentic-token-trend-audit.lock.yml index 0ed34cf78e9..8ce30b44f78 100644 --- a/.github/workflows/agentic-token-trend-audit.lock.yml +++ b/.github/workflows/agentic-token-trend-audit.lock.yml @@ -600,7 +600,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/architecture-guardian.lock.yml b/.github/workflows/architecture-guardian.lock.yml index fca70b28a0f..1dabcb92e2b 100644 --- a/.github/workflows/architecture-guardian.lock.yml +++ b/.github/workflows/architecture-guardian.lock.yml @@ -544,7 +544,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/aw-failure-investigator.lock.yml b/.github/workflows/aw-failure-investigator.lock.yml index 0472ba7c421..913c54dd38f 100644 --- a/.github/workflows/aw-failure-investigator.lock.yml +++ b/.github/workflows/aw-failure-investigator.lock.yml @@ -650,7 +650,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/bot-detection.lock.yml b/.github/workflows/bot-detection.lock.yml index 3a14518c158..0895d352196 100644 --- a/.github/workflows/bot-detection.lock.yml +++ b/.github/workflows/bot-detection.lock.yml @@ -544,7 +544,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/breaking-change-checker.lock.yml b/.github/workflows/breaking-change-checker.lock.yml index 023d21a426d..16ac432babd 100644 --- a/.github/workflows/breaking-change-checker.lock.yml +++ b/.github/workflows/breaking-change-checker.lock.yml @@ -534,7 +534,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index a8b774300c6..2befb341cda 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -680,7 +680,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 983ae5f52fd..b7fe6122c2c 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -525,7 +525,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index c4379b58ec4..9be8eccdba3 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -552,7 +552,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/codex-github-remote-mcp-test.lock.yml b/.github/workflows/codex-github-remote-mcp-test.lock.yml index 5c0ddebe845..62579a36e00 100644 --- a/.github/workflows/codex-github-remote-mcp-test.lock.yml +++ b/.github/workflows/codex-github-remote-mcp-test.lock.yml @@ -519,7 +519,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/contribution-check.lock.yml b/.github/workflows/contribution-check.lock.yml index 91a011ece04..451e2994419 100644 --- a/.github/workflows/contribution-check.lock.yml +++ b/.github/workflows/contribution-check.lock.yml @@ -654,7 +654,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/copilot-opt.lock.yml b/.github/workflows/copilot-opt.lock.yml index 10a173af164..89777007b59 100644 --- a/.github/workflows/copilot-opt.lock.yml +++ b/.github/workflows/copilot-opt.lock.yml @@ -576,7 +576,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-agentrx-trace-optimizer.lock.yml b/.github/workflows/daily-agentrx-trace-optimizer.lock.yml index 7d7683ac742..2f476255e74 100644 --- a/.github/workflows/daily-agentrx-trace-optimizer.lock.yml +++ b/.github/workflows/daily-agentrx-trace-optimizer.lock.yml @@ -660,7 +660,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-ambient-context-optimizer.lock.yml b/.github/workflows/daily-ambient-context-optimizer.lock.yml index bf9cb233f74..00168572be7 100644 --- a/.github/workflows/daily-ambient-context-optimizer.lock.yml +++ b/.github/workflows/daily-ambient-context-optimizer.lock.yml @@ -598,7 +598,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-architecture-diagram.lock.yml b/.github/workflows/daily-architecture-diagram.lock.yml index 61ee41615b4..0103efca5b6 100644 --- a/.github/workflows/daily-architecture-diagram.lock.yml +++ b/.github/workflows/daily-architecture-diagram.lock.yml @@ -619,7 +619,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" 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 d52c91dde36..b1f7ee8f042 100644 --- a/.github/workflows/daily-aw-cross-repo-compile-check.lock.yml +++ b/.github/workflows/daily-aw-cross-repo-compile-check.lock.yml @@ -550,7 +550,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml index fc92cdcf02e..1af8b586686 100644 --- a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml +++ b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml @@ -547,7 +547,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-byok-ollama-test.lock.yml b/.github/workflows/daily-byok-ollama-test.lock.yml index c506bf7cbec..b345e5708e8 100644 --- a/.github/workflows/daily-byok-ollama-test.lock.yml +++ b/.github/workflows/daily-byok-ollama-test.lock.yml @@ -521,7 +521,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-cache-strategy-analyzer.lock.yml b/.github/workflows/daily-cache-strategy-analyzer.lock.yml index b6518f3194b..81167c982e2 100644 --- a/.github/workflows/daily-cache-strategy-analyzer.lock.yml +++ b/.github/workflows/daily-cache-strategy-analyzer.lock.yml @@ -696,7 +696,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index 404d2d0e5dc..cb3efe85e49 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -638,7 +638,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-cli-tools-tester.lock.yml b/.github/workflows/daily-cli-tools-tester.lock.yml index 73d89ee01ac..2a92f0fc31c 100644 --- a/.github/workflows/daily-cli-tools-tester.lock.yml +++ b/.github/workflows/daily-cli-tools-tester.lock.yml @@ -622,7 +622,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-community-attribution.lock.yml b/.github/workflows/daily-community-attribution.lock.yml index b386c6b2e7a..1d88ca92b3e 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -613,7 +613,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-credit-limit-test.lock.yml b/.github/workflows/daily-credit-limit-test.lock.yml index 44d9bbf557e..d3f2eaf8f71 100644 --- a/.github/workflows/daily-credit-limit-test.lock.yml +++ b/.github/workflows/daily-credit-limit-test.lock.yml @@ -500,7 +500,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-doc-healer.lock.yml b/.github/workflows/daily-doc-healer.lock.yml index d912bb5e785..7c454d34f79 100644 --- a/.github/workflows/daily-doc-healer.lock.yml +++ b/.github/workflows/daily-doc-healer.lock.yml @@ -625,7 +625,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index beb5cbfd292..d26a9880195 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -569,7 +569,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index a8965cc87a8..b39cf2cd39d 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -580,7 +580,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-function-namer.lock.yml b/.github/workflows/daily-function-namer.lock.yml index 9e1880b7344..c1742237a07 100644 --- a/.github/workflows/daily-function-namer.lock.yml +++ b/.github/workflows/daily-function-namer.lock.yml @@ -654,7 +654,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-grafana-otel-instrumentation-advisor.lock.yml b/.github/workflows/daily-grafana-otel-instrumentation-advisor.lock.yml index ced006d2eff..0542b66cf95 100644 --- a/.github/workflows/daily-grafana-otel-instrumentation-advisor.lock.yml +++ b/.github/workflows/daily-grafana-otel-instrumentation-advisor.lock.yml @@ -566,7 +566,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-max-ai-credits-test.lock.yml b/.github/workflows/daily-max-ai-credits-test.lock.yml index 3d1a8ef12ad..dec63cb7cc6 100644 --- a/.github/workflows/daily-max-ai-credits-test.lock.yml +++ b/.github/workflows/daily-max-ai-credits-test.lock.yml @@ -475,7 +475,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-mcp-concurrency-analysis.lock.yml b/.github/workflows/daily-mcp-concurrency-analysis.lock.yml index e04f70d56c2..2430d509a22 100644 --- a/.github/workflows/daily-mcp-concurrency-analysis.lock.yml +++ b/.github/workflows/daily-mcp-concurrency-analysis.lock.yml @@ -627,7 +627,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-model-inventory.lock.yml b/.github/workflows/daily-model-inventory.lock.yml index fd0162c2f6e..bea4d7f1065 100644 --- a/.github/workflows/daily-model-inventory.lock.yml +++ b/.github/workflows/daily-model-inventory.lock.yml @@ -565,7 +565,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index 5a650edbace..5799cdd08c4 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -598,7 +598,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-otel-instrumentation-advisor.lock.yml b/.github/workflows/daily-otel-instrumentation-advisor.lock.yml index b6c70883f80..667b6291091 100644 --- a/.github/workflows/daily-otel-instrumentation-advisor.lock.yml +++ b/.github/workflows/daily-otel-instrumentation-advisor.lock.yml @@ -577,7 +577,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-reliability-review.lock.yml b/.github/workflows/daily-reliability-review.lock.yml index 1f90f626237..111ce28bafc 100644 --- a/.github/workflows/daily-reliability-review.lock.yml +++ b/.github/workflows/daily-reliability-review.lock.yml @@ -536,7 +536,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-safe-output-optimizer.lock.yml b/.github/workflows/daily-safe-output-optimizer.lock.yml index f2babf1caa7..21bed458a66 100644 --- a/.github/workflows/daily-safe-output-optimizer.lock.yml +++ b/.github/workflows/daily-safe-output-optimizer.lock.yml @@ -706,7 +706,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-safe-outputs-conformance.lock.yml b/.github/workflows/daily-safe-outputs-conformance.lock.yml index fdf6d901af2..ad8f85442b3 100644 --- a/.github/workflows/daily-safe-outputs-conformance.lock.yml +++ b/.github/workflows/daily-safe-outputs-conformance.lock.yml @@ -555,7 +555,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml index 5e68f1fbd71..c08d3242353 100644 --- a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml +++ b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml @@ -554,7 +554,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-security-red-team.lock.yml b/.github/workflows/daily-security-red-team.lock.yml index c670f5d9ba4..d4fc9b56ba1 100644 --- a/.github/workflows/daily-security-red-team.lock.yml +++ b/.github/workflows/daily-security-red-team.lock.yml @@ -627,7 +627,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-skill-optimizer.lock.yml b/.github/workflows/daily-skill-optimizer.lock.yml index ca0f5837378..096921944d6 100644 --- a/.github/workflows/daily-skill-optimizer.lock.yml +++ b/.github/workflows/daily-skill-optimizer.lock.yml @@ -534,7 +534,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-spdd-spec-planner.lock.yml b/.github/workflows/daily-spdd-spec-planner.lock.yml index 4d56d19315d..06b051cd31b 100644 --- a/.github/workflows/daily-spdd-spec-planner.lock.yml +++ b/.github/workflows/daily-spdd-spec-planner.lock.yml @@ -555,7 +555,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-syntax-error-quality.lock.yml b/.github/workflows/daily-syntax-error-quality.lock.yml index a306b1096e5..2636ba4fea3 100644 --- a/.github/workflows/daily-syntax-error-quality.lock.yml +++ b/.github/workflows/daily-syntax-error-quality.lock.yml @@ -536,7 +536,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index 9aba0af1671..824e2be7351 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -573,7 +573,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index 8d190bbdbf3..a892ae1745c 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -595,7 +595,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-token-consumption-report.lock.yml b/.github/workflows/daily-token-consumption-report.lock.yml index bb88c1268f3..53c4599961c 100644 --- a/.github/workflows/daily-token-consumption-report.lock.yml +++ b/.github/workflows/daily-token-consumption-report.lock.yml @@ -565,7 +565,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/daily-windows-terminal-integration-builder.lock.yml b/.github/workflows/daily-windows-terminal-integration-builder.lock.yml index a9011c682ea..76d51864d1f 100644 --- a/.github/workflows/daily-windows-terminal-integration-builder.lock.yml +++ b/.github/workflows/daily-windows-terminal-integration-builder.lock.yml @@ -503,7 +503,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index c30487e9adc..56c07e58309 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -929,7 +929,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index 3e9038385da..20f2830b5ef 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -583,7 +583,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/dependabot-burner.lock.yml b/.github/workflows/dependabot-burner.lock.yml index 34b6ada5d18..2ec2a049511 100644 --- a/.github/workflows/dependabot-burner.lock.yml +++ b/.github/workflows/dependabot-burner.lock.yml @@ -524,7 +524,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index 6fe110fd2b1..b7cbef83cc5 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -586,7 +586,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/deployment-incident-monitor.lock.yml b/.github/workflows/deployment-incident-monitor.lock.yml index 4cc19e7cf2b..a5810aa3f01 100644 --- a/.github/workflows/deployment-incident-monitor.lock.yml +++ b/.github/workflows/deployment-incident-monitor.lock.yml @@ -539,7 +539,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/designer-drift-audit.lock.yml b/.github/workflows/designer-drift-audit.lock.yml index 8d0a8bd5293..aba81bb0a65 100644 --- a/.github/workflows/designer-drift-audit.lock.yml +++ b/.github/workflows/designer-drift-audit.lock.yml @@ -508,7 +508,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 9df675cdb04..8f2b75495ee 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -579,7 +579,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/discussion-task-miner.lock.yml b/.github/workflows/discussion-task-miner.lock.yml index 15c716c4ad2..e1566484c3d 100644 --- a/.github/workflows/discussion-task-miner.lock.yml +++ b/.github/workflows/discussion-task-miner.lock.yml @@ -572,7 +572,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 9f474ab4fe7..d2f1b3be606 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -568,7 +568,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/example-permissions-warning.lock.yml b/.github/workflows/example-permissions-warning.lock.yml index e99bab226e9..f15283e3eda 100644 --- a/.github/workflows/example-permissions-warning.lock.yml +++ b/.github/workflows/example-permissions-warning.lock.yml @@ -518,7 +518,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/firewall.lock.yml b/.github/workflows/firewall.lock.yml index 55e2dca57fb..47ede9d47b3 100644 --- a/.github/workflows/firewall.lock.yml +++ b/.github/workflows/firewall.lock.yml @@ -522,7 +522,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml index f88f092c9a4..75579e62d6e 100644 --- a/.github/workflows/go-fan.lock.yml +++ b/.github/workflows/go-fan.lock.yml @@ -591,7 +591,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index 3158400f806..1dbd5d6b9ac 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -536,7 +536,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/gpclean.lock.yml b/.github/workflows/gpclean.lock.yml index 0460f28b093..29585a40477 100644 --- a/.github/workflows/gpclean.lock.yml +++ b/.github/workflows/gpclean.lock.yml @@ -595,7 +595,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/hippo-embed.lock.yml b/.github/workflows/hippo-embed.lock.yml index ffc99988d7e..c21403a2e33 100644 --- a/.github/workflows/hippo-embed.lock.yml +++ b/.github/workflows/hippo-embed.lock.yml @@ -556,7 +556,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index 4691e2b5344..5eebe80567f 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -640,7 +640,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/lint-monster.lock.yml b/.github/workflows/lint-monster.lock.yml index d7b5a05f82e..cd976082f74 100644 --- a/.github/workflows/lint-monster.lock.yml +++ b/.github/workflows/lint-monster.lock.yml @@ -602,7 +602,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index 1097fca59ec..f47c73941f0 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -623,7 +623,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/objective-impact-report.lock.yml b/.github/workflows/objective-impact-report.lock.yml index 255085af3d0..acea8084414 100644 --- a/.github/workflows/objective-impact-report.lock.yml +++ b/.github/workflows/objective-impact-report.lock.yml @@ -473,7 +473,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/otlp-data-quality-validator.lock.yml b/.github/workflows/otlp-data-quality-validator.lock.yml index f7b2f40669c..71fb20801a4 100644 --- a/.github/workflows/otlp-data-quality-validator.lock.yml +++ b/.github/workflows/otlp-data-quality-validator.lock.yml @@ -527,7 +527,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/outcome-collector.lock.yml b/.github/workflows/outcome-collector.lock.yml index 6a712489970..b9b16dd7d57 100644 --- a/.github/workflows/outcome-collector.lock.yml +++ b/.github/workflows/outcome-collector.lock.yml @@ -563,7 +563,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 2ad05a65f7f..1e20be88814 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -615,7 +615,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 06be335139a..e8e2af62ae7 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -737,7 +737,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index 1f324806781..8223646bcc0 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -603,7 +603,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/refactoring-cadence.lock.yml b/.github/workflows/refactoring-cadence.lock.yml index d0d88a19f9c..f8224ffdddc 100644 --- a/.github/workflows/refactoring-cadence.lock.yml +++ b/.github/workflows/refactoring-cadence.lock.yml @@ -555,7 +555,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/ruflo-backed-task.lock.yml b/.github/workflows/ruflo-backed-task.lock.yml index 3b034e3b64d..9b421386e12 100644 --- a/.github/workflows/ruflo-backed-task.lock.yml +++ b/.github/workflows/ruflo-backed-task.lock.yml @@ -601,7 +601,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index acac419be95..2cd442355fe 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -565,7 +565,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 54e23db60c8..309007970d1 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -580,7 +580,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index 66eeec4202b..25cefea58be 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -616,7 +616,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-antigravity.lock.yml b/.github/workflows/smoke-antigravity.lock.yml index 104582374c1..e2863a273f7 100644 --- a/.github/workflows/smoke-antigravity.lock.yml +++ b/.github/workflows/smoke-antigravity.lock.yml @@ -693,7 +693,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index 8d9f519742d..d40d96e4461 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -675,7 +675,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 3a330c0ddae..3a941a39b56 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1032,7 +1032,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 4094e1fab04..a6f1683b7f5 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -798,7 +798,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml index cae82b7e3ed..044a30ae255 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml @@ -947,7 +947,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index f277e07c518..1766a09127a 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -856,7 +856,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-copilot-sdk.lock.yml b/.github/workflows/smoke-copilot-sdk.lock.yml index 82d1fe3003a..8aa6504476b 100644 --- a/.github/workflows/smoke-copilot-sdk.lock.yml +++ b/.github/workflows/smoke-copilot-sdk.lock.yml @@ -577,7 +577,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 62233845038..c13d3072483 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -952,7 +952,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index 75690ab0e68..7f2b2e5838b 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -658,7 +658,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-crush.lock.yml b/.github/workflows/smoke-crush.lock.yml index adecc2bc389..5870c96dfaa 100644 --- a/.github/workflows/smoke-crush.lock.yml +++ b/.github/workflows/smoke-crush.lock.yml @@ -632,7 +632,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index a58d59b1a65..6d07d40c329 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -697,7 +697,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index a0d4eecbea5..810b3cc23bb 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -636,7 +636,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-otel-backends.lock.yml b/.github/workflows/smoke-otel-backends.lock.yml index d6334aa5a55..4d30358f917 100644 --- a/.github/workflows/smoke-otel-backends.lock.yml +++ b/.github/workflows/smoke-otel-backends.lock.yml @@ -623,7 +623,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-pi.lock.yml b/.github/workflows/smoke-pi.lock.yml index cb5501705c8..1ca5e0daba0 100644 --- a/.github/workflows/smoke-pi.lock.yml +++ b/.github/workflows/smoke-pi.lock.yml @@ -664,7 +664,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index 0b6c5ae34a2..37da00ab3d2 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -677,7 +677,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-temporary-id.lock.yml b/.github/workflows/smoke-temporary-id.lock.yml index 72c7394e79f..e90cf37dd61 100644 --- a/.github/workflows/smoke-temporary-id.lock.yml +++ b/.github/workflows/smoke-temporary-id.lock.yml @@ -646,7 +646,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 77b35c7f4a9..8d6a5ca9818 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -691,7 +691,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/smoke-workflow-call-with-inputs.lock.yml b/.github/workflows/smoke-workflow-call-with-inputs.lock.yml index 35b588107d0..bcc34ef9977 100644 --- a/.github/workflows/smoke-workflow-call-with-inputs.lock.yml +++ b/.github/workflows/smoke-workflow-call-with-inputs.lock.yml @@ -605,7 +605,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/spec-librarian.lock.yml b/.github/workflows/spec-librarian.lock.yml index 0f62d095e6d..8448adde481 100644 --- a/.github/workflows/spec-librarian.lock.yml +++ b/.github/workflows/spec-librarian.lock.yml @@ -569,7 +569,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index 40ec37d9c65..d21133464aa 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -704,7 +704,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index b0f0507fec9..68dd51096f2 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -666,7 +666,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/step-name-alignment.lock.yml b/.github/workflows/step-name-alignment.lock.yml index 9c70f988a1c..4c72f51fb3a 100644 --- a/.github/workflows/step-name-alignment.lock.yml +++ b/.github/workflows/step-name-alignment.lock.yml @@ -550,7 +550,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index 32fa0a13b9b..423610b2eeb 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -566,7 +566,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/test-workflow.lock.yml b/.github/workflows/test-workflow.lock.yml index 67a90973cc5..d5e441b4117 100644 --- a/.github/workflows/test-workflow.lock.yml +++ b/.github/workflows/test-workflow.lock.yml @@ -519,7 +519,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index 1d31be06fde..b253aa9f57b 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -537,7 +537,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 29e8331f81e..af19cdd776d 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -583,7 +583,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/workflow-normalizer.lock.yml b/.github/workflows/workflow-normalizer.lock.yml index d4aa87885e9..9d765d77996 100644 --- a/.github/workflows/workflow-normalizer.lock.yml +++ b/.github/workflows/workflow-normalizer.lock.yml @@ -592,7 +592,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" diff --git a/.github/workflows/workflow-skill-extractor.lock.yml b/.github/workflows/workflow-skill-extractor.lock.yml index f41d94c9d79..10957a917d6 100644 --- a/.github/workflows/workflow-skill-extractor.lock.yml +++ b/.github/workflows/workflow-skill-extractor.lock.yml @@ -559,7 +559,8 @@ jobs: "required": true, "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 65000, + "minLength": 20 }, "fields": { "type": "array" From a5e69ca64c8fa09c8651de9d7016f5f5aeabd1b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:40:13 +0000 Subject: [PATCH 3/8] Fix checkout manifest step template-injection regression Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/build_checkout_manifest.cjs | 126 +++++++++++++++++ .../setup/js/build_checkout_manifest.test.cjs | 129 ++++++++++++++++++ pkg/workflow/checkout_manager_test.go | 60 ++++---- .../checkout_manifest_compile_test.go | 52 +++++++ pkg/workflow/checkout_step_generator.go | 55 +++----- pkg/workflow/compiler_yaml_main_job.go | 2 +- 6 files changed, 365 insertions(+), 59 deletions(-) create mode 100644 actions/setup/js/build_checkout_manifest.cjs create mode 100644 actions/setup/js/build_checkout_manifest.test.cjs create mode 100644 pkg/workflow/checkout_manifest_compile_test.go diff --git a/actions/setup/js/build_checkout_manifest.cjs b/actions/setup/js/build_checkout_manifest.cjs new file mode 100644 index 00000000000..b824ae14bbe --- /dev/null +++ b/actions/setup/js/build_checkout_manifest.cjs @@ -0,0 +1,126 @@ +// @ts-check +/// + +const fs = require("fs"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +const { getErrorMessage } = require("./error_helpers.cjs"); + +function parseManifestEntries(entriesJSON = process.env.GH_AW_CHECKOUT_MANIFEST_ENTRIES || "[]") { + const parsed = JSON.parse(entriesJSON); + if (!Array.isArray(parsed)) { + throw new Error("GH_AW_CHECKOUT_MANIFEST_ENTRIES must be a JSON array"); + } + return parsed; +} + +function readManifestEntriesFromEnv() { + const count = Number.parseInt(process.env.GH_AW_CHECKOUT_MANIFEST_COUNT || "0", 10); + if (!Number.isFinite(count) || count < 0) { + throw new Error("GH_AW_CHECKOUT_MANIFEST_COUNT must be a non-negative integer"); + } + + const entries = []; + for (let i = 0; i < count; i += 1) { + entries.push({ + repository: process.env[`GH_AW_CHECKOUT_REPO_${i}`] || "", + path: process.env[`GH_AW_CHECKOUT_PATH_${i}`] || "", + }); + } + return entries; +} + +function resolveDefaultBranch(repository, checkoutPath, options = {}) { + const workspace = options.workspace || process.env.GITHUB_WORKSPACE || ""; + const runGit = options.runGit || ((args, execOptions = {}) => execFileSync("git", args, { encoding: "utf8", ...execOptions })); + const runGH = options.runGH || ((args, execOptions = {}) => execFileSync("gh", args, { encoding: "utf8", ...execOptions })); + let defaultBranch = ""; + + const repoPath = checkoutPath ? path.join(workspace, checkoutPath) : workspace; + if (repoPath && fs.existsSync(path.join(repoPath, ".git"))) { + try { + const output = runGit(["-C", repoPath, "symbolic-ref", "--short", "refs/remotes/origin/HEAD"], { + stdio: ["ignore", "pipe", "pipe"], + }); + defaultBranch = output.trim().replace(/^origin\//, ""); + } catch (error) { + if (typeof core !== "undefined") { + core.debug(`build_checkout_manifest: git default branch lookup failed for ${repository}: ${getErrorMessage(error)}`); + } + } + } + + if (defaultBranch === "") { + try { + defaultBranch = runGH(["api", `repos/${repository}`, "--jq", ".default_branch"], { + stdio: ["ignore", "pipe", "pipe"], + }).trim(); + } catch (error) { + if (typeof core !== "undefined") { + core.debug(`build_checkout_manifest: gh api default branch lookup failed for ${repository}: ${getErrorMessage(error)}`); + } + } + } + + return defaultBranch; +} + +function buildCheckoutManifest(entries, options = {}) { + const runnerTemp = options.runnerTemp || process.env.RUNNER_TEMP; + if (!runnerTemp) { + throw new Error("RUNNER_TEMP is required to build checkout manifest"); + } + + const runGit = options.runGit; + const runGH = options.runGH; + + const manifestDir = path.join(runnerTemp, "gh-aw"); + fs.mkdirSync(manifestDir, { recursive: true }); + const manifestPath = path.join(manifestDir, "checkout-manifest.json"); + const manifest = {}; + + for (const entry of entries) { + if (!entry || typeof entry !== "object") continue; + const repository = String(entry.repository || "").trim(); + if (repository === "") continue; + const checkoutPath = String(entry.path || ""); + const defaultBranch = resolveDefaultBranch(repository, checkoutPath, { + workspace: options.workspace, + runGit, + runGH, + }); + manifest[repository.toLowerCase()] = { + repository, + path: checkoutPath, + default_branch: defaultBranch, + }; + if (typeof core !== "undefined") { + core.info(`checkout-manifest: ${repository} -> path=${checkoutPath} default_branch=${defaultBranch || ""}`); + } + } + + fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8"); + if (typeof core !== "undefined") { + core.info(`checkout-manifest written to ${manifestPath}`); + } + return { manifestPath, manifest }; +} + +async function main(options = {}) { + let entries; + if (typeof options.entriesJSON === "string" && options.entriesJSON.trim() !== "") { + entries = parseManifestEntries(options.entriesJSON); + } else { + entries = readManifestEntriesFromEnv(); + } + return buildCheckoutManifest(entries, options); +} + +module.exports = { + buildCheckoutManifest, + main, + parseManifestEntries, + readManifestEntriesFromEnv, + resolveDefaultBranch, +}; diff --git a/actions/setup/js/build_checkout_manifest.test.cjs b/actions/setup/js/build_checkout_manifest.test.cjs new file mode 100644 index 00000000000..9115ff2d9be --- /dev/null +++ b/actions/setup/js/build_checkout_manifest.test.cjs @@ -0,0 +1,129 @@ +import { afterEach, describe, expect, it } from "vitest"; +import fs from "fs"; +import os from "os"; +import path from "path"; +import { spawnSync } from "child_process"; + +import { buildCheckoutManifest, readManifestEntriesFromEnv, resolveDefaultBranch } from "./build_checkout_manifest.cjs"; + +function execGit(args, options = {}) { + const result = spawnSync("git", args, { encoding: "utf8", ...options }); + if (result.error) throw result.error; + if (result.status !== 0) { + throw new Error(`git ${args.join(" ")} failed:\nstdout: ${result.stdout}\nstderr: ${result.stderr}`); + } + return result.stdout; +} + +function createTempDir(prefix) { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} + +function removeDir(dir) { + if (dir && fs.existsSync(dir)) { + fs.rmSync(dir, { recursive: true, force: true }); + } +} + +function setEnv(key, value) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } +} + +describe("build_checkout_manifest.cjs", () => { + const originalEnv = { ...process.env }; + const tempDirs = []; + + afterEach(() => { + for (const key of Object.keys(process.env)) { + if (!(key in originalEnv)) { + delete process.env[key]; + } + } + Object.assign(process.env, originalEnv); + while (tempDirs.length > 0) { + removeDir(tempDirs.pop()); + } + }); + + it("reads entries from environment variables", () => { + setEnv("GH_AW_CHECKOUT_MANIFEST_COUNT", "2"); + setEnv("GH_AW_CHECKOUT_REPO_0", "owner/a"); + setEnv("GH_AW_CHECKOUT_PATH_0", "./a"); + setEnv("GH_AW_CHECKOUT_REPO_1", "owner/b"); + setEnv("GH_AW_CHECKOUT_PATH_1", ""); + + expect(readManifestEntriesFromEnv()).toEqual([ + { repository: "owner/a", path: "./a" }, + { repository: "owner/b", path: "" }, + ]); + }); + + it("resolves default branch from local git checkout before gh fallback", () => { + const workspace = createTempDir("checkout-manifest-workspace-"); + tempDirs.push(workspace); + const checkoutPath = "target"; + const repoDir = path.join(workspace, checkoutPath); + fs.mkdirSync(repoDir, { recursive: true }); + + execGit(["init", "-q"], { cwd: repoDir }); + execGit(["symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/main"], { cwd: repoDir }); + + const ghCalls = []; + const defaultBranch = resolveDefaultBranch("owner/repo", checkoutPath, { + workspace, + runGH: args => { + ghCalls.push(args); + return "should-not-be-used\n"; + }, + }); + + expect(defaultBranch).toBe("main"); + expect(ghCalls).toHaveLength(0); + }); + + it("falls back to gh api when local git default branch is unavailable", () => { + const workspace = createTempDir("checkout-manifest-workspace-"); + tempDirs.push(workspace); + + const defaultBranch = resolveDefaultBranch("owner/repo", "missing", { + workspace, + runGH: () => "trunk\n", + }); + + expect(defaultBranch).toBe("trunk"); + }); + + it("writes manifest with lowercase keys", () => { + const workspace = createTempDir("checkout-manifest-workspace-"); + const runnerTemp = createTempDir("checkout-manifest-runner-temp-"); + tempDirs.push(workspace, runnerTemp); + + const { manifestPath, manifest } = buildCheckoutManifest( + [ + { repository: "Owner/Repo", path: "./repo" }, + { repository: "", path: "./skip" }, + ], + { + workspace, + runnerTemp, + runGH: () => "main\n", + } + ); + + expect(manifestPath).toBe(path.join(runnerTemp, "gh-aw", "checkout-manifest.json")); + expect(manifest).toEqual({ + "owner/repo": { + repository: "Owner/Repo", + path: "./repo", + default_branch: "main", + }, + }); + + const fileContents = JSON.parse(fs.readFileSync(manifestPath, "utf8")); + expect(fileContents).toEqual(manifest); + }); +}); diff --git a/pkg/workflow/checkout_manager_test.go b/pkg/workflow/checkout_manager_test.go index 43a5838b362..7275bfd8043 100644 --- a/pkg/workflow/checkout_manager_test.go +++ b/pkg/workflow/checkout_manager_test.go @@ -1376,59 +1376,62 @@ func TestWikiCheckout(t *testing.T) { // The manifest step is consumed by the safe-outputs MCP server to resolve a // per-repo default branch without making any network calls at request time. func TestGenerateCheckoutManifestStep(t *testing.T) { + getActionPin := func(action string) string { + return action + "@pin" + } + t.Run("no configs emits nothing", func(t *testing.T) { cm := NewCheckoutManager(nil) - assert.Empty(t, cm.GenerateCheckoutManifestStep(), "empty manager should not emit a manifest step") + assert.Empty(t, cm.GenerateCheckoutManifestStep(getActionPin), "empty manager should not emit a manifest step") }) t.Run("default-only checkout emits nothing", func(t *testing.T) { cm := NewCheckoutManager([]*CheckoutConfig{ {Path: "."}, }) - assert.Empty(t, cm.GenerateCheckoutManifestStep(), "manifest is for cross-repo entries only; default checkout should not produce one") + assert.Empty(t, cm.GenerateCheckoutManifestStep(getActionPin), "manifest is for cross-repo entries only; default checkout should not produce one") }) t.Run("path-only additional checkout (no repository) emits nothing", func(t *testing.T) { cm := NewCheckoutManager([]*CheckoutConfig{ {Path: "./workspace"}, }) - assert.Empty(t, cm.GenerateCheckoutManifestStep(), "additional checkout without repository should not be in manifest") + assert.Empty(t, cm.GenerateCheckoutManifestStep(getActionPin), "additional checkout without repository should not be in manifest") }) t.Run("wiki cross-repo checkout is excluded", func(t *testing.T) { cm := NewCheckoutManager([]*CheckoutConfig{ {Repository: "owner/docs", Path: "./wiki", Wiki: true}, }) - assert.Empty(t, cm.GenerateCheckoutManifestStep(), "wiki checkouts must be excluded from manifest") + assert.Empty(t, cm.GenerateCheckoutManifestStep(getActionPin), "wiki checkouts must be excluded from manifest") }) t.Run("cross-repo additional checkout emits entry", func(t *testing.T) { cm := NewCheckoutManager([]*CheckoutConfig{ {Repository: "owner/other", Path: "./other"}, }) - steps := cm.GenerateCheckoutManifestStep() + steps := cm.GenerateCheckoutManifestStep(getActionPin) require.Len(t, steps, 1, "should emit one manifest step") out := steps[0] assert.Contains(t, out, "name: Build checkout manifest for safe-outputs handlers") - assert.Contains(t, out, "${RUNNER_TEMP}/gh-aw/checkout-manifest.json") + assert.Contains(t, out, "uses: actions/github-script@pin") assert.Contains(t, out, "GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}") - assert.Contains(t, out, "resolve_default_branch", "should define the resolver helper") - assert.Contains(t, out, "git -C \"${GITHUB_WORKSPACE}/${path}\" symbolic-ref --short refs/remotes/origin/HEAD", "should attempt local git resolution first") - assert.Contains(t, out, "gh api \"repos/${repo}\" --jq '.default_branch'", "should fall back to gh api") - assert.Contains(t, out, "repo='owner/other'", "repo must be shell-single-quoted") - assert.Contains(t, out, "path='./other'", "path must be shell-single-quoted") - assert.Contains(t, out, "ascii_downcase", "manifest key must be lowercased via jq") + assert.Contains(t, out, `GH_AW_CHECKOUT_MANIFEST_COUNT: "1"`) + assert.Contains(t, out, `GH_AW_CHECKOUT_REPO_0: "owner/other"`) + assert.Contains(t, out, `GH_AW_CHECKOUT_PATH_0: "./other"`) + assert.Contains(t, out, "build_checkout_manifest.cjs") + assert.NotContains(t, out, "run: |", "manifest step should use github-script instead of shell run block") }) t.Run("cross-repo root checkout (empty path) is included", func(t *testing.T) { cm := NewCheckoutManager([]*CheckoutConfig{ {Repository: "owner/other"}, }) - steps := cm.GenerateCheckoutManifestStep() + steps := cm.GenerateCheckoutManifestStep(getActionPin) require.Len(t, steps, 1, "cross-repo root checkout must be in manifest") out := steps[0] - assert.Contains(t, out, "repo='owner/other'") - assert.Contains(t, out, "path=''", "empty path should still be emitted (manifest consumer expects the key)") + assert.Contains(t, out, `GH_AW_CHECKOUT_REPO_0: "owner/other"`) + assert.Contains(t, out, `GH_AW_CHECKOUT_PATH_0: ""`, "empty path should still be emitted (manifest consumer expects the key)") }) t.Run("multiple cross-repo entries each get a jq update", func(t *testing.T) { @@ -1438,24 +1441,31 @@ func TestGenerateCheckoutManifestStep(t *testing.T) { {Path: "./local-only"}, // no repo → skipped {Repository: "owner/c", Path: "./c"}, // included }) - steps := cm.GenerateCheckoutManifestStep() + steps := cm.GenerateCheckoutManifestStep(getActionPin) require.Len(t, steps, 1) out := steps[0] - assert.Equal(t, 3, strings.Count(out, "resolve_default_branch \"$repo\" \"$path\""), "should invoke resolver once per cross-repo entry") - assert.Contains(t, out, "repo='owner/a'") - assert.Contains(t, out, "repo='owner/b'") - assert.Contains(t, out, "repo='owner/c'") + assert.Contains(t, out, `GH_AW_CHECKOUT_MANIFEST_COUNT: "3"`) + assert.Contains(t, out, `GH_AW_CHECKOUT_REPO_0: "owner/a"`) + assert.Contains(t, out, `GH_AW_CHECKOUT_REPO_1: "owner/b"`) + assert.Contains(t, out, `GH_AW_CHECKOUT_REPO_2: "owner/c"`) assert.NotContains(t, out, "./local-only", "path-only entries must not be in the manifest step") }) - t.Run("repository names containing single quotes are escaped", func(t *testing.T) { - // Repository names cannot actually contain single quotes per GitHub rules, - // but the shellSingleQuote helper must still defend against it. + t.Run("repository names containing single quotes are yaml-escaped", func(t *testing.T) { cm := NewCheckoutManager([]*CheckoutConfig{ {Repository: "weird'owner/repo", Path: "./x"}, }) - steps := cm.GenerateCheckoutManifestStep() + steps := cm.GenerateCheckoutManifestStep(getActionPin) + require.Len(t, steps, 1) + assert.Contains(t, steps[0], `GH_AW_CHECKOUT_REPO_0: "weird'owner/repo"`) + }) + + t.Run("dynamic repository expressions are emitted as raw env expressions", func(t *testing.T) { + cm := NewCheckoutManager([]*CheckoutConfig{ + {Repository: "${{ github.event.inputs.trigger_ref }}", Path: "./target"}, + }) + steps := cm.GenerateCheckoutManifestStep(getActionPin) require.Len(t, steps, 1) - assert.Contains(t, steps[0], `repo='weird'\''owner/repo'`, "single quote must be escaped with '\\''") + assert.Contains(t, steps[0], "GH_AW_CHECKOUT_REPO_0: ${{ github.event.inputs.trigger_ref }}") }) } diff --git a/pkg/workflow/checkout_manifest_compile_test.go b/pkg/workflow/checkout_manifest_compile_test.go new file mode 100644 index 00000000000..ed92d453a16 --- /dev/null +++ b/pkg/workflow/checkout_manifest_compile_test.go @@ -0,0 +1,52 @@ +//go:build !integration + +package workflow + +import ( + "os" + "path/filepath" + "testing" + + "github.com/github/gh-aw/pkg/stringutil" + "github.com/stretchr/testify/require" +) + +func TestCompileDynamicCheckoutRepositoryManifestStepUsesGitHubScript(t *testing.T) { + tmpDir := t.TempDir() + workflowPath := filepath.Join(tmpDir, "repro.md") + content := `--- +on: + workflow_dispatch: + inputs: + trigger_ref: + type: string + required: true +engine: copilot +timeout-minutes: 5 +checkout: + - repository: ${{ github.event.inputs.trigger_ref }} + current: true +safe-outputs: + noop: + report-as-issue: false +--- +# Repro +Do nothing. +` + require.NoError(t, os.WriteFile(workflowPath, []byte(content), 0o644)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(workflowPath)) + + lockPath := stringutil.MarkdownToLockFile(workflowPath) + lockBytes, err := os.ReadFile(lockPath) + require.NoError(t, err) + lock := string(lockBytes) + + require.Contains(t, lock, "name: Build checkout manifest for safe-outputs handlers") + require.Contains(t, lock, "uses: actions/github-script") + require.Contains(t, lock, "build_checkout_manifest.cjs") + require.Contains(t, lock, "GH_AW_CHECKOUT_REPO_0: ${{ github.event.inputs.trigger_ref }}") + require.NotContains(t, lock, "repo='${{ github.event.inputs.trigger_ref }}'") + require.NotContains(t, lock, "Build checkout manifest for safe-outputs handlers\n run: |", "manifest step must not use run block") +} diff --git a/pkg/workflow/checkout_step_generator.go b/pkg/workflow/checkout_step_generator.go index 45020086ac8..f9d0f5f9cf0 100644 --- a/pkg/workflow/checkout_step_generator.go +++ b/pkg/workflow/checkout_step_generator.go @@ -2,6 +2,7 @@ package workflow import ( "fmt" + "strconv" "strings" ) @@ -79,7 +80,7 @@ func (cm *CheckoutManager) GenerateAdditionalCheckoutSteps(getActionPin func(str // 2. `gh api repos// --jq .default_branch` as a credentialed fallback // // Returns an empty slice when there are no non-default cross-repo checkouts to record. -func (cm *CheckoutManager) GenerateCheckoutManifestStep() []string { +func (cm *CheckoutManager) GenerateCheckoutManifestStep(getActionPin func(string) string) []string { type manifestEntry struct { repository string path string @@ -102,43 +103,31 @@ func (cm *CheckoutManager) GenerateCheckoutManifestStep() []string { var sb strings.Builder sb.WriteString(" - name: Build checkout manifest for safe-outputs handlers\n") + fmt.Fprintf(&sb, " uses: %s\n", getActionPin("actions/github-script")) sb.WriteString(" env:\n") sb.WriteString(" GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}\n") - sb.WriteString(" run: |\n") - sb.WriteString(" set -euo pipefail\n") - sb.WriteString(" mkdir -p \"${RUNNER_TEMP}/gh-aw\"\n") - sb.WriteString(" manifest=\"${RUNNER_TEMP}/gh-aw/checkout-manifest.json\"\n") - sb.WriteString(" printf '{}' > \"$manifest\"\n") - sb.WriteString(" resolve_default_branch() {\n") - sb.WriteString(" local repo=\"$1\" path=\"$2\" db=\"\"\n") - sb.WriteString(" if [ -d \"${GITHUB_WORKSPACE}/${path}/.git\" ]; then\n") - sb.WriteString(" db=$(git -C \"${GITHUB_WORKSPACE}/${path}\" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||' || true)\n") - sb.WriteString(" fi\n") - sb.WriteString(" if [ -z \"$db\" ]; then\n") - sb.WriteString(" db=$(gh api \"repos/${repo}\" --jq '.default_branch' 2>/dev/null || true)\n") - sb.WriteString(" fi\n") - sb.WriteString(" printf '%s' \"$db\"\n") - sb.WriteString(" }\n") - for _, e := range entries { - // Both repo and path are static at compile time. Use shell-quoted literals. - fmt.Fprintf(&sb, " repo=%s\n", shellSingleQuote(e.repository)) - fmt.Fprintf(&sb, " path=%s\n", shellSingleQuote(e.path)) - sb.WriteString(" db=$(resolve_default_branch \"$repo\" \"$path\")\n") - sb.WriteString(" tmp=$(mktemp)\n") - sb.WriteString(" jq --arg repo \"$repo\" --arg path \"$path\" --arg db \"$db\" \\\n") - sb.WriteString(" '.[($repo | ascii_downcase)] = {repository: $repo, path: $path, default_branch: $db}' \\\n") - sb.WriteString(" \"$manifest\" > \"$tmp\" && mv \"$tmp\" \"$manifest\"\n") - sb.WriteString(" echo \"checkout-manifest: ${repo} -> path=${path} default_branch=${db:-}\"\n") - } - sb.WriteString(" cat \"$manifest\"\n") + writeYAMLEnv(&sb, " ", "GH_AW_CHECKOUT_MANIFEST_COUNT", strconv.Itoa(len(entries))) + for i, e := range entries { + repoKey := fmt.Sprintf("GH_AW_CHECKOUT_REPO_%d", i) + pathKey := fmt.Sprintf("GH_AW_CHECKOUT_PATH_%d", i) + if strings.Contains(e.repository, "${{") { + fmt.Fprintf(&sb, " %s: %s\n", repoKey, githubExpressionWhitespaceReplacer.Replace(e.repository)) + } else { + writeYAMLEnv(&sb, " ", repoKey, e.repository) + } + if strings.Contains(e.path, "${{") { + fmt.Fprintf(&sb, " %s: %s\n", pathKey, githubExpressionWhitespaceReplacer.Replace(e.path)) + } else { + writeYAMLEnv(&sb, " ", pathKey, e.path) + } + } + sb.WriteString(" with:\n") + sb.WriteString(" script: |\n") + sb.WriteString(" const { main } = require('${{ runner.temp }}/gh-aw/actions/build_checkout_manifest.cjs');\n") + sb.WriteString(" await main();\n") return []string{sb.String()} } -// shellSingleQuote returns s wrapped in single quotes, escaping any embedded single quotes. -func shellSingleQuote(s string) string { - return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" -} - // GenerateGitHubFolderCheckoutStep generates YAML step lines for a sparse checkout of // the .github and .agents folders. This is used in the activation job to access workflow // configuration and runtime imports. diff --git a/pkg/workflow/compiler_yaml_main_job.go b/pkg/workflow/compiler_yaml_main_job.go index f0fbb29f3b4..7043017e68e 100644 --- a/pkg/workflow/compiler_yaml_main_job.go +++ b/pkg/workflow/compiler_yaml_main_job.go @@ -143,7 +143,7 @@ func (c *Compiler) generateInitialAndCheckoutSteps(yaml *strings.Builder, data * // Emit a manifest step that records the path and resolved default branch for each // non-default cross-repo checkout. The safe-outputs MCP server reads this file to // resolve base branches without making any credentialed network calls. - for _, line := range checkoutMgr.GenerateCheckoutManifestStep() { + for _, line := range checkoutMgr.GenerateCheckoutManifestStep(c.getActionPin) { yaml.WriteString(line) } From 45bcda4e8dfaa237e49e3f4d1394f05636dcb134 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:13:06 +0000 Subject: [PATCH 4/8] chore: plan follow-up for review feedback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../smoke-create-cross-repo-pr.lock.yml | 32 +++++-------------- .../smoke-update-cross-repo-pr.lock.yml | 32 +++++-------------- 2 files changed, 16 insertions(+), 48 deletions(-) diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index 7f2b2e5838b..7d6f7720071 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -508,32 +508,16 @@ jobs: repository: github/gh-aw-side-repo token: ${{ secrets.GH_AW_SIDE_REPO_PAT }} - name: Build checkout manifest for safe-outputs handlers + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - mkdir -p "${RUNNER_TEMP}/gh-aw" - manifest="${RUNNER_TEMP}/gh-aw/checkout-manifest.json" - printf '{}' > "$manifest" - resolve_default_branch() { - local repo="$1" path="$2" db="" - if [ -d "${GITHUB_WORKSPACE}/${path}/.git" ]; then - db=$(git -C "${GITHUB_WORKSPACE}/${path}" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||' || true) - fi - if [ -z "$db" ]; then - db=$(gh api "repos/${repo}" --jq '.default_branch' 2>/dev/null || true) - fi - printf '%s' "$db" - } - repo='github/gh-aw-side-repo' - path='' - db=$(resolve_default_branch "$repo" "$path") - tmp=$(mktemp) - jq --arg repo "$repo" --arg path "$path" --arg db "$db" \ - '.[($repo | ascii_downcase)] = {repository: $repo, path: $path, default_branch: $db}' \ - "$manifest" > "$tmp" && mv "$tmp" "$manifest" - echo "checkout-manifest: ${repo} -> path=${path} default_branch=${db:-}" - cat "$manifest" + GH_AW_CHECKOUT_MANIFEST_COUNT: "1" + GH_AW_CHECKOUT_REPO_0: "github/gh-aw-side-repo" + GH_AW_CHECKOUT_PATH_0: "" + with: + script: | + const { main } = require('${{ runner.temp }}/gh-aw/actions/build_checkout_manifest.cjs'); + await main(); - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 8d6a5ca9818..c1790922973 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -527,32 +527,16 @@ jobs: header=$(printf "x-access-token:%s" "${GH_AW_FETCH_TOKEN}" | base64 -w 0) git -c "http.extraheader=Authorization: Basic ${header}" fetch origin '+refs/heads/main:refs/remotes/origin/main' '+refs/pull/*/head:refs/remotes/origin/pull/*/head' - name: Build checkout manifest for safe-outputs handlers + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - mkdir -p "${RUNNER_TEMP}/gh-aw" - manifest="${RUNNER_TEMP}/gh-aw/checkout-manifest.json" - printf '{}' > "$manifest" - resolve_default_branch() { - local repo="$1" path="$2" db="" - if [ -d "${GITHUB_WORKSPACE}/${path}/.git" ]; then - db=$(git -C "${GITHUB_WORKSPACE}/${path}" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||' || true) - fi - if [ -z "$db" ]; then - db=$(gh api "repos/${repo}" --jq '.default_branch' 2>/dev/null || true) - fi - printf '%s' "$db" - } - repo='github/gh-aw-side-repo' - path='' - db=$(resolve_default_branch "$repo" "$path") - tmp=$(mktemp) - jq --arg repo "$repo" --arg path "$path" --arg db "$db" \ - '.[($repo | ascii_downcase)] = {repository: $repo, path: $path, default_branch: $db}' \ - "$manifest" > "$tmp" && mv "$tmp" "$manifest" - echo "checkout-manifest: ${repo} -> path=${path} default_branch=${db:-}" - cat "$manifest" + GH_AW_CHECKOUT_MANIFEST_COUNT: "1" + GH_AW_CHECKOUT_REPO_0: "github/gh-aw-side-repo" + GH_AW_CHECKOUT_PATH_0: "" + with: + script: | + const { main } = require('${{ runner.temp }}/gh-aw/actions/build_checkout_manifest.cjs'); + await main(); - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise From 71f094a0f5fc07e90957c652e5437d601efb4c90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:20:29 +0000 Subject: [PATCH 5/8] Add checkout manifest logging and shim core usage Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/build_checkout_manifest.cjs | 31 +++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/actions/setup/js/build_checkout_manifest.cjs b/actions/setup/js/build_checkout_manifest.cjs index b824ae14bbe..8b4c826761f 100644 --- a/actions/setup/js/build_checkout_manifest.cjs +++ b/actions/setup/js/build_checkout_manifest.cjs @@ -1,6 +1,8 @@ // @ts-check /// +require("./shim.cjs"); + const fs = require("fs"); const path = require("path"); const { execFileSync } = require("child_process"); @@ -44,10 +46,9 @@ function resolveDefaultBranch(repository, checkoutPath, options = {}) { stdio: ["ignore", "pipe", "pipe"], }); defaultBranch = output.trim().replace(/^origin\//, ""); + core.debug(`build_checkout_manifest: git resolved default branch for ${repository}: ${defaultBranch}`); } catch (error) { - if (typeof core !== "undefined") { - core.debug(`build_checkout_manifest: git default branch lookup failed for ${repository}: ${getErrorMessage(error)}`); - } + core.debug(`build_checkout_manifest: git default branch lookup failed for ${repository}: ${getErrorMessage(error)}`); } } @@ -56,10 +57,9 @@ function resolveDefaultBranch(repository, checkoutPath, options = {}) { defaultBranch = runGH(["api", `repos/${repository}`, "--jq", ".default_branch"], { stdio: ["ignore", "pipe", "pipe"], }).trim(); + core.debug(`build_checkout_manifest: gh api resolved default branch for ${repository}: ${defaultBranch}`); } catch (error) { - if (typeof core !== "undefined") { - core.debug(`build_checkout_manifest: gh api default branch lookup failed for ${repository}: ${getErrorMessage(error)}`); - } + core.debug(`build_checkout_manifest: gh api default branch lookup failed for ${repository}: ${getErrorMessage(error)}`); } } @@ -79,11 +79,18 @@ function buildCheckoutManifest(entries, options = {}) { fs.mkdirSync(manifestDir, { recursive: true }); const manifestPath = path.join(manifestDir, "checkout-manifest.json"); const manifest = {}; + core.info(`checkout-manifest: building manifest for ${entries.length} checkout entries`); for (const entry of entries) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + core.debug("checkout-manifest: skipping non-object entry"); + continue; + } const repository = String(entry.repository || "").trim(); - if (repository === "") continue; + if (repository === "") { + core.debug("checkout-manifest: skipping entry with empty repository"); + continue; + } const checkoutPath = String(entry.path || ""); const defaultBranch = resolveDefaultBranch(repository, checkoutPath, { workspace: options.workspace, @@ -95,15 +102,11 @@ function buildCheckoutManifest(entries, options = {}) { path: checkoutPath, default_branch: defaultBranch, }; - if (typeof core !== "undefined") { - core.info(`checkout-manifest: ${repository} -> path=${checkoutPath} default_branch=${defaultBranch || ""}`); - } + core.info(`checkout-manifest: ${repository} -> path=${checkoutPath} default_branch=${defaultBranch || ""}`); } fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8"); - if (typeof core !== "undefined") { - core.info(`checkout-manifest written to ${manifestPath}`); - } + core.info(`checkout-manifest written to ${manifestPath}`); return { manifestPath, manifest }; } From 627d66bc2a50e10edc429986ece5449bdc050fea Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 9 Jun 2026 18:27:59 +0100 Subject: [PATCH 6/8] Add checkout-credential-review skill and wire from AGENTS.md --- .../checkout-credential-review/SKILL.md | 36 +++++++++++++++++++ AGENTS.md | 1 + 2 files changed, 37 insertions(+) create mode 100644 .github/skills/checkout-credential-review/SKILL.md diff --git a/.github/skills/checkout-credential-review/SKILL.md b/.github/skills/checkout-credential-review/SKILL.md new file mode 100644 index 00000000000..30e559f74e4 --- /dev/null +++ b/.github/skills/checkout-credential-review/SKILL.md @@ -0,0 +1,36 @@ +--- +name: checkout-credential-review +description: Review code that performs git or gh operations against repository checkouts in gh-aw, checking that the right credentials are available at the right time and that sparseness, shallowness and credential-free factors are properly considered. +--- + +# Checkout Credential Review + +Use this skill when reviewing or writing code in `pkg/workflow/`, `actions/setup/js/`, or compiled `.lock.yml` workflows that runs `git`, `gh`, or any other remote-touching operation against a repository checkout. + +## Background + +Each entry in a workflow's `checkout:` block may declare its own credentials (`github-token:`, `github-app:`), and the compiler wires those into the corresponding `actions/checkout` step ([pkg/workflow/checkout_step_generator.go](pkg/workflow/checkout_step_generator.go)). Generated checkouts always set `persist-credentials: false`, so the on-disk repo retains **no** credentials after the step finishes — only `actions/checkout`'s own internal token is used during the clone, and it is scrubbed in its post-step. + +A separate step that wants to authenticate later must either (a) re-inject a token at command level (e.g. `git -c http.extraheader=...`) or (b) be passed the per-checkout token via env. The compiler does *not* automatically thread per-checkout `github-token`s into downstream steps. + +Two important contexts deliberately run with **no git credentials**: + +- The **safe-outputs MCP server** and its handlers (`generate_git_bundle.cjs`, `generate_git_patch.cjs`, `create_pull_request.cjs`). Errors in these paths explicitly say "the safe-outputs MCP server has no credentials for private repositories" — fetch/push will fail for private repos. +- The **agent runtime** after `actions/checkout`. The agent prompt in [actions/setup/md/safe_outputs_push_to_pr_branch.md](actions/setup/md/safe_outputs_push_to_pr_branch.md) explicitly tells the model not to attempt `git fetch`, `git pull`, `git push`, or any other authenticated git operation, and to report unavailable branches rather than try to fetch them. + +## Review checklist + +When you see a new `git`, `gh`, `execFileSync('git'…)`, or compiled `run:` block: + +1. **Does it touch a remote?** Local-only commands (`symbolic-ref`, `rev-parse`, `log`, `show`, `merge-base`, `diff`, `status`) need no credentials. Anything in `fetch | pull | push | clone | ls-remote | remote (set-url|add|update)` does, plus on-demand blob fetches in partial clones. +2. **Which checkout is it operating on?** If it's a cross-repo entry from `checkout:`, the relevant credential is *that entry's* `github-token`, not the workflow's default `GITHUB_TOKEN`. Confirm the per-entry token is actually threaded into the step's env (or refuse to do remote operations and degrade gracefully). +3. **Which job/context emits it?** Agent job and safe-outputs MCP server both run without git credentials by design. Any remote git operation there must be wrapped in `try/catch`, fail soft, and surface a clear "no credentials" error rather than a raw git stderr. +4. **Sparse / shallow / monorepo concerns.** Avoid emitting steps that deepen (`git fetch --unshallow`, `--deepen=N`) or widen (`git fetch origin '+refs/heads/*'`) a sparse or shallow checkout of a large monorepo — these need credentials *and* can pull hundreds of MB. Prefer expanding `fetch:` / `fetch-depth:` / `sparse-checkout:` at compile time so it happens during `actions/checkout` with its internal token, never later. +5. **`gh` is REST, not git.** `gh api …` uses whatever `GH_TOKEN` is in the step's env — it does **not** automatically inherit per-checkout PATs. For cross-org private repos, either thread the right token in or accept the call will 404 and handle it. + +## Related + +- [docs/src/content/docs/reference/checkout.md](docs/src/content/docs/reference/checkout.md) — "Git Credentials After Checkout" +- [docs/sparseness.md](docs/sparseness.md) — sparse/blobless credential lifecycle +- [pkg/workflow/checkout_step_generator.go](pkg/workflow/checkout_step_generator.go) — token wiring per checkout +- [actions/setup/md/safe_outputs_push_to_pr_branch.md](actions/setup/md/safe_outputs_push_to_pr_branch.md) — agent-facing guidance diff --git a/AGENTS.md b/AGENTS.md index 195392da6f8..a6439ed93e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,6 +50,7 @@ Use skills only when the task requires specialized guidance. Do not pre-load eve - GitHub MCP usage patterns → `.github/skills/github-mcp-server/SKILL.md` - Query helpers for issues/PRs/workflows/discussions/labels → matching `.github/skills/github-*-query/SKILL.md` - Doc-writing conventions → `.github/skills/documentation/SKILL.md` +- Reviewing or writing `git`/`gh`/remote operations against checkouts (per-checkout credentials, sparse/shallow monorepos, safe-outputs MCP runs without credentials) → `.github/skills/checkout-credential-review/SKILL.md` ## Why this file is intentionally short From 5a208c91ed5226ee3e673fd050752fa2a9c1944f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:44:43 +0000 Subject: [PATCH 7/8] Use per-checkout tokens for checkout-manifest gh fallback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/build_checkout_manifest.cjs | 11 +++- .../setup/js/build_checkout_manifest.test.cjs | 13 ++++- pkg/workflow/checkout_manager_test.go | 20 ++++++- pkg/workflow/checkout_step_generator.go | 56 +++++++++++-------- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/actions/setup/js/build_checkout_manifest.cjs b/actions/setup/js/build_checkout_manifest.cjs index 8b4c826761f..c267d07aa4f 100644 --- a/actions/setup/js/build_checkout_manifest.cjs +++ b/actions/setup/js/build_checkout_manifest.cjs @@ -28,6 +28,7 @@ function readManifestEntriesFromEnv() { entries.push({ repository: process.env[`GH_AW_CHECKOUT_REPO_${i}`] || "", path: process.env[`GH_AW_CHECKOUT_PATH_${i}`] || "", + token: process.env[`GH_AW_CHECKOUT_TOKEN_${i}`] || "", }); } return entries; @@ -54,9 +55,14 @@ function resolveDefaultBranch(repository, checkoutPath, options = {}) { if (defaultBranch === "") { try { - defaultBranch = runGH(["api", `repos/${repository}`, "--jq", ".default_branch"], { + const checkoutToken = String(options.checkoutToken || ""); + const ghExecOptions = { stdio: ["ignore", "pipe", "pipe"], - }).trim(); + }; + if (checkoutToken !== "") { + ghExecOptions.env = { ...process.env, GH_TOKEN: checkoutToken }; + } + defaultBranch = runGH(["api", `repos/${repository}`, "--jq", ".default_branch"], ghExecOptions).trim(); core.debug(`build_checkout_manifest: gh api resolved default branch for ${repository}: ${defaultBranch}`); } catch (error) { core.debug(`build_checkout_manifest: gh api default branch lookup failed for ${repository}: ${getErrorMessage(error)}`); @@ -96,6 +102,7 @@ function buildCheckoutManifest(entries, options = {}) { workspace: options.workspace, runGit, runGH, + checkoutToken: String(entry.token || ""), }); manifest[repository.toLowerCase()] = { repository, diff --git a/actions/setup/js/build_checkout_manifest.test.cjs b/actions/setup/js/build_checkout_manifest.test.cjs index 9115ff2d9be..1da95cb1635 100644 --- a/actions/setup/js/build_checkout_manifest.test.cjs +++ b/actions/setup/js/build_checkout_manifest.test.cjs @@ -53,12 +53,13 @@ describe("build_checkout_manifest.cjs", () => { setEnv("GH_AW_CHECKOUT_MANIFEST_COUNT", "2"); setEnv("GH_AW_CHECKOUT_REPO_0", "owner/a"); setEnv("GH_AW_CHECKOUT_PATH_0", "./a"); + setEnv("GH_AW_CHECKOUT_TOKEN_0", "${{ secrets.REPO_A_TOKEN }}"); setEnv("GH_AW_CHECKOUT_REPO_1", "owner/b"); setEnv("GH_AW_CHECKOUT_PATH_1", ""); expect(readManifestEntriesFromEnv()).toEqual([ - { repository: "owner/a", path: "./a" }, - { repository: "owner/b", path: "" }, + { repository: "owner/a", path: "./a", token: "${{ secrets.REPO_A_TOKEN }}" }, + { repository: "owner/b", path: "", token: "" }, ]); }); @@ -88,13 +89,19 @@ describe("build_checkout_manifest.cjs", () => { it("falls back to gh api when local git default branch is unavailable", () => { const workspace = createTempDir("checkout-manifest-workspace-"); tempDirs.push(workspace); + let ghOptions = null; const defaultBranch = resolveDefaultBranch("owner/repo", "missing", { workspace, - runGH: () => "trunk\n", + checkoutToken: "${{ secrets.CROSS_REPO_PAT }}", + runGH: (_args, options) => { + ghOptions = options; + return "trunk\n"; + }, }); expect(defaultBranch).toBe("trunk"); + expect(ghOptions?.env?.GH_TOKEN).toBe("${{ secrets.CROSS_REPO_PAT }}"); }); it("writes manifest with lowercase keys", () => { diff --git a/pkg/workflow/checkout_manager_test.go b/pkg/workflow/checkout_manager_test.go index 7275bfd8043..9ab8c22d4fe 100644 --- a/pkg/workflow/checkout_manager_test.go +++ b/pkg/workflow/checkout_manager_test.go @@ -1408,7 +1408,7 @@ func TestGenerateCheckoutManifestStep(t *testing.T) { t.Run("cross-repo additional checkout emits entry", func(t *testing.T) { cm := NewCheckoutManager([]*CheckoutConfig{ - {Repository: "owner/other", Path: "./other"}, + {Repository: "owner/other", Path: "./other", GitHubToken: "${{ secrets.CROSS_REPO_PAT }}"}, }) steps := cm.GenerateCheckoutManifestStep(getActionPin) require.Len(t, steps, 1, "should emit one manifest step") @@ -1419,6 +1419,7 @@ func TestGenerateCheckoutManifestStep(t *testing.T) { assert.Contains(t, out, `GH_AW_CHECKOUT_MANIFEST_COUNT: "1"`) assert.Contains(t, out, `GH_AW_CHECKOUT_REPO_0: "owner/other"`) assert.Contains(t, out, `GH_AW_CHECKOUT_PATH_0: "./other"`) + assert.Contains(t, out, "GH_AW_CHECKOUT_TOKEN_0: ${{ secrets.CROSS_REPO_PAT }}") assert.Contains(t, out, "build_checkout_manifest.cjs") assert.NotContains(t, out, "run: |", "manifest step should use github-script instead of shell run block") }) @@ -1468,4 +1469,21 @@ func TestGenerateCheckoutManifestStep(t *testing.T) { require.Len(t, steps, 1) assert.Contains(t, steps[0], "GH_AW_CHECKOUT_REPO_0: ${{ github.event.inputs.trigger_ref }}") }) + + t.Run("github-app token expression preserves checkout index in manifest env", func(t *testing.T) { + cm := NewCheckoutManager([]*CheckoutConfig{ + {Path: "./local"}, + { + Repository: "owner/private", + Path: "./private", + GitHubApp: &GitHubAppConfig{ + AppID: "${{ vars.APP_ID }}", + PrivateKey: "${{ secrets.APP_PRIVATE_KEY }}", + }, + }, + }) + steps := cm.GenerateCheckoutManifestStep(getActionPin) + require.Len(t, steps, 1) + assert.Contains(t, steps[0], "GH_AW_CHECKOUT_TOKEN_0: ${{ steps.checkout-app-token-1.outputs.token }}") + }) } diff --git a/pkg/workflow/checkout_step_generator.go b/pkg/workflow/checkout_step_generator.go index f9d0f5f9cf0..66ecf56a5df 100644 --- a/pkg/workflow/checkout_step_generator.go +++ b/pkg/workflow/checkout_step_generator.go @@ -84,16 +84,21 @@ func (cm *CheckoutManager) GenerateCheckoutManifestStep(getActionPin func(string type manifestEntry struct { repository string path string + token string } var entries []manifestEntry - for _, entry := range cm.ordered { + for i, entry := range cm.ordered { if entry.key.wiki { continue } if entry.key.repository == "" { continue } - entries = append(entries, manifestEntry{repository: entry.key.repository, path: entry.key.path}) + entries = append(entries, manifestEntry{ + repository: entry.key.repository, + path: entry.key.path, + token: resolveCheckoutTokenExpression(entry, i, false), + }) } if len(entries) == 0 { return nil @@ -120,6 +125,14 @@ func (cm *CheckoutManager) GenerateCheckoutManifestStep(getActionPin func(string } else { writeYAMLEnv(&sb, " ", pathKey, e.path) } + if e.token != "" { + tokenKey := fmt.Sprintf("GH_AW_CHECKOUT_TOKEN_%d", i) + if strings.Contains(e.token, "${{") { + fmt.Fprintf(&sb, " %s: %s\n", tokenKey, githubExpressionWhitespaceReplacer.Replace(e.token)) + } else { + writeYAMLEnv(&sb, " ", tokenKey, e.token) + } + } } sb.WriteString(" with:\n") sb.WriteString(" script: |\n") @@ -328,15 +341,7 @@ func generateCheckoutStepLines(entry *resolvedCheckout, index int, getActionPin fmt.Fprintf(&sb, " path: %s\n", entry.key.path) } // Determine effective token: github-app-minted token takes precedence - effectiveToken := entry.token - if entry.githubApp != nil { - // The token is minted in the agent job itself (same-job step reference). - //nolint:gosec // G101: False positive - this is a GitHub Actions expression template placeholder, not a hardcoded credential - effectiveToken = fmt.Sprintf("${{ steps.checkout-app-token-%d.outputs.token }}", index) - if entry.githubApp.shouldIgnoreMissingKey() { - effectiveToken = combineTokenExpressions(effectiveToken, getEffectiveGitHubToken(entry.token)) - } - } + effectiveToken := resolveCheckoutTokenExpression(entry, index, false) if effectiveToken != "" { fmt.Fprintf(&sb, " token: %s\n", effectiveToken) } @@ -458,18 +463,7 @@ func generateFetchStepLines(entry *resolvedCheckout, index int) string { } // Determine authentication token - token := entry.token - if entry.githubApp != nil { - // The token is minted in the agent job itself (same-job step reference). - //nolint:gosec // G101: False positive - this is a GitHub Actions expression template placeholder, not a hardcoded credential - token = fmt.Sprintf("${{ steps.checkout-app-token-%d.outputs.token }}", index) - if entry.githubApp.shouldIgnoreMissingKey() { - token = combineTokenExpressions(token, getEffectiveGitHubToken(entry.token)) - } - } - if token == "" { - token = getEffectiveGitHubToken("") - } + token := resolveCheckoutTokenExpression(entry, index, true) // Build refspecs refspecs := make([]string, 0, len(entry.fetchRefs)) @@ -507,3 +501,19 @@ func generateFetchStepLines(entry *resolvedCheckout, index int) string { gitPrefix, depthFlag, strings.Join(refspecs, " ")) return sb.String() } + +func resolveCheckoutTokenExpression(entry *resolvedCheckout, index int, defaultWhenEmpty bool) string { + token := entry.token + if entry.githubApp != nil { + // The token is minted in the agent job itself (same-job step reference). + //nolint:gosec // G101: False positive - this is a GitHub Actions expression template placeholder, not a hardcoded credential + token = fmt.Sprintf("${{ steps.checkout-app-token-%d.outputs.token }}", index) + if entry.githubApp.shouldIgnoreMissingKey() { + token = combineTokenExpressions(token, getEffectiveGitHubToken(entry.token)) + } + } + if token == "" && defaultWhenEmpty { + token = getEffectiveGitHubToken("") + } + return token +} From ba29dbf9fc043ba93759c2c41fa188c344a2bab9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:58:44 +0000 Subject: [PATCH 8/8] Use per-checkout tokens in checkout manifest fallback and recompile Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../smoke-create-cross-repo-pr.lock.yml | 1 + .../smoke-update-cross-repo-pr.lock.yml | 1 + actions/setup/js/build_checkout_manifest.cjs | 15 +++++++--- pkg/workflow/checkout_step_generator.go | 28 +++++++++---------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index 7d6f7720071..33690526074 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -514,6 +514,7 @@ jobs: GH_AW_CHECKOUT_MANIFEST_COUNT: "1" GH_AW_CHECKOUT_REPO_0: "github/gh-aw-side-repo" GH_AW_CHECKOUT_PATH_0: "" + GH_AW_CHECKOUT_TOKEN_0: ${{ secrets.GH_AW_SIDE_REPO_PAT }} with: script: | const { main } = require('${{ runner.temp }}/gh-aw/actions/build_checkout_manifest.cjs'); diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index c1790922973..0ccc6399f1e 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -533,6 +533,7 @@ jobs: GH_AW_CHECKOUT_MANIFEST_COUNT: "1" GH_AW_CHECKOUT_REPO_0: "github/gh-aw-side-repo" GH_AW_CHECKOUT_PATH_0: "" + GH_AW_CHECKOUT_TOKEN_0: ${{ secrets.GH_AW_SIDE_REPO_PAT }} with: script: | const { main } = require('${{ runner.temp }}/gh-aw/actions/build_checkout_manifest.cjs'); diff --git a/actions/setup/js/build_checkout_manifest.cjs b/actions/setup/js/build_checkout_manifest.cjs index c267d07aa4f..0e77a571407 100644 --- a/actions/setup/js/build_checkout_manifest.cjs +++ b/actions/setup/js/build_checkout_manifest.cjs @@ -37,7 +37,14 @@ function readManifestEntriesFromEnv() { function resolveDefaultBranch(repository, checkoutPath, options = {}) { const workspace = options.workspace || process.env.GITHUB_WORKSPACE || ""; const runGit = options.runGit || ((args, execOptions = {}) => execFileSync("git", args, { encoding: "utf8", ...execOptions })); - const runGH = options.runGH || ((args, execOptions = {}) => execFileSync("gh", args, { encoding: "utf8", ...execOptions })); + const runGH = + options.runGH || + ((args, execOptions = {}) => + execFileSync("gh", args, { + encoding: "utf8", + env: { ...process.env, ...(execOptions.env || {}) }, + ...execOptions, + })); let defaultBranch = ""; const repoPath = checkoutPath ? path.join(workspace, checkoutPath) : workspace; @@ -55,12 +62,12 @@ function resolveDefaultBranch(repository, checkoutPath, options = {}) { if (defaultBranch === "") { try { - const checkoutToken = String(options.checkoutToken || ""); + const checkoutToken = options.checkoutToken || ""; const ghExecOptions = { stdio: ["ignore", "pipe", "pipe"], }; if (checkoutToken !== "") { - ghExecOptions.env = { ...process.env, GH_TOKEN: checkoutToken }; + ghExecOptions.env = { GH_TOKEN: checkoutToken }; } defaultBranch = runGH(["api", `repos/${repository}`, "--jq", ".default_branch"], ghExecOptions).trim(); core.debug(`build_checkout_manifest: gh api resolved default branch for ${repository}: ${defaultBranch}`); @@ -102,7 +109,7 @@ function buildCheckoutManifest(entries, options = {}) { workspace: options.workspace, runGit, runGH, - checkoutToken: String(entry.token || ""), + checkoutToken: entry.token || "", }); manifest[repository.toLowerCase()] = { repository, diff --git a/pkg/workflow/checkout_step_generator.go b/pkg/workflow/checkout_step_generator.go index 66ecf56a5df..11ef5c81d47 100644 --- a/pkg/workflow/checkout_step_generator.go +++ b/pkg/workflow/checkout_step_generator.go @@ -31,11 +31,11 @@ func wikiRepository(repository string) string { func (cm *CheckoutManager) GenerateCheckoutAppTokenSteps(c *Compiler, permissions *Permissions) []string { checkoutManagerLog.Printf("Building app token minting steps for %d checkout entries", len(cm.ordered)) var steps []string - for i, entry := range cm.ordered { + for checkoutIndex, entry := range cm.ordered { if entry.githubApp == nil { continue } - checkoutManagerLog.Printf("Generating app token minting step for checkout index=%d repo=%q", i, entry.key.repository) + checkoutManagerLog.Printf("Generating app token minting step for checkout index=%d repo=%q", checkoutIndex, entry.key.repository) // Pass empty fallback so the app token defaults to github.event.repository.name. // Checkout-specific cross-repo scoping is handled via the explicit repository field. steps = append(steps, c.buildGitHubAppTokenMintStepWithMeta( @@ -43,8 +43,8 @@ func (cm *CheckoutManager) GenerateCheckoutAppTokenSteps(c *Compiler, permission permissions, "", entry.key.repository, - fmt.Sprintf("Generate GitHub App token for checkout (%d)", i), - fmt.Sprintf("checkout-app-token-%d", i), + fmt.Sprintf("Generate GitHub App token for checkout (%d)", checkoutIndex), + fmt.Sprintf("checkout-app-token-%d", checkoutIndex), )...) } return steps @@ -56,12 +56,12 @@ func (cm *CheckoutManager) GenerateCheckoutAppTokenSteps(c *Compiler, permission func (cm *CheckoutManager) GenerateAdditionalCheckoutSteps(getActionPin func(string) string) []string { checkoutManagerLog.Printf("Generating additional checkout steps from %d configured entries", len(cm.ordered)) var lines []string - for i, entry := range cm.ordered { + for checkoutIndex, entry := range cm.ordered { // Skip the default checkout (handled separately) if entry.key.path == "" && entry.key.repository == "" { continue } - lines = append(lines, generateCheckoutStepLines(entry, i, getActionPin)...) + lines = append(lines, generateCheckoutStepLines(entry, checkoutIndex, getActionPin)...) } checkoutManagerLog.Printf("Generated %d additional checkout step(s)", len(lines)) return lines @@ -87,7 +87,7 @@ func (cm *CheckoutManager) GenerateCheckoutManifestStep(getActionPin func(string token string } var entries []manifestEntry - for i, entry := range cm.ordered { + for checkoutIndex, entry := range cm.ordered { if entry.key.wiki { continue } @@ -97,7 +97,7 @@ func (cm *CheckoutManager) GenerateCheckoutManifestStep(getActionPin func(string entries = append(entries, manifestEntry{ repository: entry.key.repository, path: entry.key.path, - token: resolveCheckoutTokenExpression(entry, i, false), + token: resolveCheckoutTokenExpression(entry, checkoutIndex, false), }) } if len(entries) == 0 { @@ -112,9 +112,9 @@ func (cm *CheckoutManager) GenerateCheckoutManifestStep(getActionPin func(string sb.WriteString(" env:\n") sb.WriteString(" GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}\n") writeYAMLEnv(&sb, " ", "GH_AW_CHECKOUT_MANIFEST_COUNT", strconv.Itoa(len(entries))) - for i, e := range entries { - repoKey := fmt.Sprintf("GH_AW_CHECKOUT_REPO_%d", i) - pathKey := fmt.Sprintf("GH_AW_CHECKOUT_PATH_%d", i) + for manifestIndex, e := range entries { + repoKey := fmt.Sprintf("GH_AW_CHECKOUT_REPO_%d", manifestIndex) + pathKey := fmt.Sprintf("GH_AW_CHECKOUT_PATH_%d", manifestIndex) if strings.Contains(e.repository, "${{") { fmt.Fprintf(&sb, " %s: %s\n", repoKey, githubExpressionWhitespaceReplacer.Replace(e.repository)) } else { @@ -126,7 +126,7 @@ func (cm *CheckoutManager) GenerateCheckoutManifestStep(getActionPin func(string writeYAMLEnv(&sb, " ", pathKey, e.path) } if e.token != "" { - tokenKey := fmt.Sprintf("GH_AW_CHECKOUT_TOKEN_%d", i) + tokenKey := fmt.Sprintf("GH_AW_CHECKOUT_TOKEN_%d", manifestIndex) if strings.Contains(e.token, "${{") { fmt.Fprintf(&sb, " %s: %s\n", tokenKey, githubExpressionWhitespaceReplacer.Replace(e.token)) } else { @@ -502,12 +502,12 @@ func generateFetchStepLines(entry *resolvedCheckout, index int) string { return sb.String() } -func resolveCheckoutTokenExpression(entry *resolvedCheckout, index int, defaultWhenEmpty bool) string { +func resolveCheckoutTokenExpression(entry *resolvedCheckout, checkoutIndex int, defaultWhenEmpty bool) string { token := entry.token if entry.githubApp != nil { // The token is minted in the agent job itself (same-job step reference). //nolint:gosec // G101: False positive - this is a GitHub Actions expression template placeholder, not a hardcoded credential - token = fmt.Sprintf("${{ steps.checkout-app-token-%d.outputs.token }}", index) + token = fmt.Sprintf("${{ steps.checkout-app-token-%d.outputs.token }}", checkoutIndex) if entry.githubApp.shouldIgnoreMissingKey() { token = combineTokenExpressions(token, getEffectiveGitHubToken(entry.token)) }