diff --git a/scripts/ci/postprocess-smoke-workflows.test.ts b/scripts/ci/postprocess-smoke-workflows.test.ts index e94e51d8..d86cd4f5 100644 --- a/scripts/ci/postprocess-smoke-workflows.test.ts +++ b/scripts/ci/postprocess-smoke-workflows.test.ts @@ -245,6 +245,9 @@ const sessionStateDirInjectionRegex = const copySessionStateStepRegex = /^(\s+)- name: Copy Copilot session state files to logs\n\1 if: always\(\)\n\1 continue-on-error: true\n\1 run: bash "\$\{RUNNER_TEMP\}\/gh-aw\/actions\/copy_copilot_session_state\.sh"\n/m; +const copilotModelEmptyFallbackRegex = + /(COPILOT_MODEL:\s*\$\{\{\s*vars\.GH_AW_MODEL_AGENT_COPILOT\s*\|\|\s*)''(\s*\}\})/g; + function buildCopySessionStateStep(indent: string): string { const i = indent; const ri = `${i} `; @@ -355,3 +358,31 @@ describe('buildCopySessionStateStep', () => { }); }); +describe('copilotModelEmptyFallbackRegex', () => { + const EXPECTED_COPILOT_MODEL_FALLBACK = 'claude-opus-4.6'; + + beforeEach(() => { + copilotModelEmptyFallbackRegex.lastIndex = 0; + }); + + it('should replace empty fallback with claude-opus-4.6 fallback', () => { + const input = " COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}\n"; + const result = input.replace( + copilotModelEmptyFallbackRegex, + `$1'${EXPECTED_COPILOT_MODEL_FALLBACK}'$2` + ); + expect(result).toBe( + ` COPILOT_MODEL: \${{ vars.GH_AW_MODEL_AGENT_COPILOT || '${EXPECTED_COPILOT_MODEL_FALLBACK}' }}\n` + ); + }); + + it('should not modify already-correct fallback', () => { + const input = + " COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-opus-4.6' }}\n"; + const result = input.replace( + copilotModelEmptyFallbackRegex, + `$1'${EXPECTED_COPILOT_MODEL_FALLBACK}'$2` + ); + expect(result).toBe(input); + }); +}); diff --git a/scripts/ci/postprocess-smoke-workflows.ts b/scripts/ci/postprocess-smoke-workflows.ts index 07ef5920..06ba7945 100644 --- a/scripts/ci/postprocess-smoke-workflows.ts +++ b/scripts/ci/postprocess-smoke-workflows.ts @@ -94,6 +94,14 @@ const sessionStateDirInjectionRegex = /--audit-dir \/tmp\/gh-aw\/sandbox\/firewall\/audit(?! --session-state-dir)/g; const SESSION_STATE_DIR = '/tmp/gh-aw/sandbox/agent/session-state'; +// Work around gh-aw compiler bug (gh-aw#26565) where Copilot model fallback is +// emitted as an empty string: +// COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} +// In BYOK smoke workflows, this overrides workflow-level COPILOT_MODEL when the +// repo variable is unset, causing Copilot CLI startup failure. +const copilotModelEmptyFallbackRegex = + /(COPILOT_MODEL:\s*\$\{\{\s*vars\.GH_AW_MODEL_AGENT_COPILOT\s*\|\|\s*)''(\s*\}\})/g; + // Sentinel used to detect whether the "Copy Copilot session state" step has // already been replaced with the AWF-aware inline script. const copySessionStateSentinel = 'SESSION_STATE_SRC='; @@ -445,6 +453,24 @@ for (const workflowPath of workflowPaths) { } } + // For smoke-copilot-byok: replace empty model fallbacks with the workflow- + // level COPILOT_MODEL env so the generated step inherits the shared default + // without hardcoding a duplicate model string here. + const isCopilotByokSmoke = workflowPath.includes('smoke-copilot-byok.lock.yml'); + if (isCopilotByokSmoke) { + const emptyFallbackMatches = content.match(copilotModelEmptyFallbackRegex); + if (emptyFallbackMatches) { + content = content.replace( + copilotModelEmptyFallbackRegex, + '$1env.COPILOT_MODEL$2' + ); + modified = true; + console.log( + ` Replaced ${emptyFallbackMatches.length} empty COPILOT_MODEL fallback(s) for BYOK smoke` + ); + } + } + // For smoke-services: inject GitHub Actions services block (Redis + PostgreSQL) into the // agent job and replace --enable-host-access with --allow-host-service-ports 6379,5432. // The gh-aw compiler does not natively support GitHub Actions `services:` in the