Treat expression-backed engine.model as omitted when empty#46777
Conversation
When engine.model or model is a pure GitHub Actions expression (e.g.,
${{ vars.MY_MODEL }}), it may resolve to an empty string at runtime if
the variable is unset. Previously this empty value was propagated directly
to the engine, causing failures.
This change wraps pure expression-backed model values with the normal
fallback chain for Copilot, Claude, and Codex engines, so that an empty
resolution falls back to org/enterprise variables and the built-in default.
For Copilot: ${{ vars.MY_MODEL || vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
For Claude: ${{ inputs.model || vars.GH_AW_MODEL_AGENT_CLAUDE || vars.GH_AW_DEFAULT_MODEL_CLAUDE || '' }}
For Codex: ${{ inputs.model || vars.GH_AW_MODEL_AGENT_CODEX || vars.GH_AW_DEFAULT_MODEL_CODEX || 'gpt-5.4' }}
Changes:
- pkg/workflow/compilerenv/manager.go: add BuildModelOverrideExpressionWithUserOverride
- pkg/workflow/expression_patterns.go: add extractExpressionInner helper
- pkg/workflow/copilot_engine_execution.go: wrap expressions in addCopilotModelEnv
- pkg/workflow/claude_engine.go: wrap expressions in model env assignment
- pkg/workflow/codex_engine.go: wrap expressions in model env assignment
- Updated/added tests
- Recompiled 4 internal workflows using expression-backed models
Closes #46499
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes #46499 by adding standard model fallbacks when a pure model expression resolves empty.
Changes:
- Adds expression extraction and fallback-chain construction.
- Applies fallback resolution to Copilot, Claude, and Codex.
- Updates tests and generated workflows.
Composite expressions are currently misclassified as pure expressions and rewritten unexpectedly.
Show a summary per file
| File | Description |
|---|---|
pkg/workflow/model_env_vars_test.go |
Updates model fallback expectations. |
pkg/workflow/expression_patterns.go |
Adds expression-content extraction. |
pkg/workflow/copilot_engine_execution.go |
Adds Copilot expression fallbacks. |
pkg/workflow/compilerenv/manager.go |
Builds combined fallback expressions. |
pkg/workflow/compilerenv/manager_test.go |
Tests fallback construction. |
pkg/workflow/codex_engine.go |
Adds Codex expression fallbacks. |
pkg/workflow/claude_engine.go |
Adds Claude expression fallbacks. |
.github/workflows/test-quality-sentinel.lock.yml |
Regenerates Copilot model configuration. |
.github/workflows/daily-doc-healer.lock.yml |
Regenerates Claude model configuration. |
.github/workflows/daily-caveman-optimizer.lock.yml |
Regenerates Claude model configuration. |
.github/workflows/daily-cache-strategy-analyzer.lock.yml |
Regenerates Codex model configuration. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 11/11 changed files
- Comments generated: 4
- Review effort level: Medium
| if isExpression(workflowData.Model) { | ||
| innerExpr := extractExpressionInner(workflowData.Model) | ||
| expr := compilerenv.BuildModelOverrideExpressionWithUserOverride(innerExpr, modelEnvVar, compilerenv.DefaultModelCopilot, constants.CopilotBYOKDefaultModel) |
There was a problem hiding this comment.
Addressed by the later fallback-env implementation and now covered explicitly in a915121: composite values are kept verbatim in COPILOT_MODEL, and the runtime uses separate GH_AW_MODEL_FALLBACK when the resolved model is empty.
| if isExpression(workflowData.Model) { | ||
| innerExpr := extractExpressionInner(workflowData.Model) | ||
| isDetectionJob := workflowData.SafeOutputs == nil |
There was a problem hiding this comment.
Addressed in a915121: Claude keeps composite values like ${{ inputs.provider }}/${{ inputs.model }} unchanged, and this pass adds the matching regression test in pkg/workflow/model_env_vars_test.go.
| if isExpression(workflowData.Model) { | ||
| innerExpr := extractExpressionInner(workflowData.Model) | ||
| expr := compilerenv.BuildModelOverrideExpressionWithUserOverride(innerExpr, modelEnvVar, compilerenv.DefaultModelCodex, constants.CodexDefaultModel) |
There was a problem hiding this comment.
Addressed by the current fallback-env path and confirmed in a915121: composite interpolation stays in the original model env value, while GH_AW_MODEL_FALLBACK is only used when the runtime sees an empty resolved model.
| // fallback chain. Use this when engine.model or model is a GitHub Actions expression that | ||
| // may resolve to an empty string (e.g. ${{ vars.MY_MODEL }} when the variable is unset). | ||
| // userExprInner is the raw content between "${{" and "}}" of the user expression. | ||
| // When builtinFallback is empty the expression ends with || ”. |
There was a problem hiding this comment.
Already satisfied on the current branch and still true in a915121: the empty fallback is '', and pkg/workflow/compilerenv/manager_test.go asserts that exact string.
|
@copilot add a fallback env var with the expression that can be used in JavaScript if the model expression resolves to empty. That way it handles any syntax |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Addressed in 0ce2115 (with follow-up fix in f2740c3). The runtime now keeps the configured model expression intact and adds |
|
[pr-sous-chef] @copilot please run the Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "pi.dev"See Network Configuration for more information.
|
Latest maintainer feedback was already covered by |
|
@copilot merge main and recompile |
…treat-empty-model-as-omitted Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Addressed in |
Addressed in
Fixed in |
🤖 PR Triage
Score breakdown: impact 30 + urgency 20 + quality 15 Rationale: Empty expression for Run §29743972799
|
Fixed on top of that run in |
|
@copilot please run the Unresolved review threads:
Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "pi.dev"See Network Configuration for more information.
|
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Addressed in |
|
@copilot handle evals job |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Addressed in |
|
🎉 This pull request is included in a new release. Release: |
When
engine.model(or top-levelmodel) is a GitHub Actions expression like${{ vars.HOLISTIC_REVIEW_MODEL }}and the variable is unset, the expression resolves to""at runtime. Previously that empty string was propagated directly to the engine, making the variable mandatory rather than optional.Changes
compilerenv/manager.go— newBuildModelOverrideExpressionWithUserOverride(userExprInner, primaryVar, enterpriseDefaultVar, builtinFallback)that prepends a user expression to the standard org/enterprise/built-in fallback chainexpression_patterns.go— newextractExpressionInner(s)helper with an explicit length guard that strips${{/}}delimiters safelycopilot_engine_execution.go,claude_engine.go,codex_engine.go— whenmodelis a pure expression (isExpression()== true), wrap it instead of passing it through directly:Only pure expressions are affected (
${{ ... }}). Literal model values (e.g.gpt-4) and partial expressions (e.g.provider/${{ vars.MODEL }}) retain existing behavior.Four internal workflows using
${{ needs.activation.outputs.model_size }}as their model were recompiled to pick up the updated output.run: https://github.com/github/gh-aw/actions/runs/29735453773
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
pi.devSee Network Configuration for more information.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
pi.devSee Network Configuration for more information.