Follow up to Agent 'needs' does not incorporate jobs in engine.env expressions (#30232) (fixed in #30239) and Activation 'needs' does not incorporate jobs in engine.env expressions (#30790).
When an engine.env value contains a needs.<job>.outputs.* expression, the auto-generated detection (threat-detection) job reuses that same engine.env mapping but does not gain a needs entry for the referenced custom job. Inside the detection job, needs.<job> is therefore undefined, so the value resolves to empty even though the agent job received the intended value.
The agent job (fixed in #30239), the activation job (via the #30790 on.permissions: {} workaround), and the conclusion job all correctly gain the custom job in their needs. Only the detection job is left out, and none of the known workarounds coerce it in.
The example below sets COPILOT_MODEL from a select_model job that runs earlier in the workflow, but the same happens for any engine.env value built from a needs.<job>.outputs.* expression.
repro-detection-needs-bug.md
---
engine:
id: copilot
env:
COPILOT_MODEL: ${{ needs.select_model.outputs.model }}
jobs:
select_model:
needs: [pre_activation]
runs-on: ubuntu-slim
steps:
- id: pick
shell: bash
run: echo "model=claude-sonnet-4.6" >> "$GITHUB_OUTPUT"
outputs:
model: ${{ steps.pick.outputs.model }}
safe-outputs:
create-issue:
on:
permissions: {}
workflow_dispatch:
---
# Repro detection needs bug
safe-outputs auto-enables threat detection, so a detection job is generated. The on.permissions: {} + needs: [pre_activation] workaround from #30790 is applied so that activation, agent, and conclusion all correctly gain select_model, isolating detection as the only job that does not.
Expected Result
The detection job gains select_model in its needs, matching agent and conclusion:
jobs:
detection:
needs:
- activation
- agent
- select_model
# ...
Actual Result
detection is missing select_model, even though its engine step still sets COPILOT_MODEL from needs.select_model:
jobs:
detection:
needs:
- activation
- agent
# ...
steps:
# ...
- env:
COPILOT_MODEL: ${{ needs.select_model.outputs.model }}
# REPRO NOTE: `needs.select_model` is `undefined` here, so `COPILOT_MODEL` resolves to empty
For comparison, every other job that consumes the expression gains select_model:
jobs:
activation:
needs: [pre_activation, select_model]
agent:
needs: [activation, select_model]
conclusion:
needs: [activation, agent, detection, safe_outputs, select_model]
select_model:
needs: pre_activation
No known workaround
Impact
Any workflow that derives an engine.env value from an earlier job's output--for example, selecting the model dynamically--gets that value silently blanked in the threat-detection job. Detection then runs with a different engine configuration than the agent, and there is no compile-time error or warning because a needs.<job> reference outside the job's needs list resolves to empty rather than failing.
Suggested Fix
Extend the engine.env needs-expression scan added in #30239 (and applied to activation per #30790) so that the detection job's needs list is populated from the same needs.<job>.outputs.* references. The detection job inherits the engine.env mapping, so it should inherit the corresponding job dependencies as well.
Follow up to Agent 'needs' does not incorporate jobs in engine.env expressions (#30232) (fixed in #30239) and Activation 'needs' does not incorporate jobs in engine.env expressions (#30790).
When an
engine.envvalue contains aneeds.<job>.outputs.*expression, the auto-generateddetection(threat-detection) job reuses that sameengine.envmapping but does not gain aneedsentry for the referenced custom job. Inside thedetectionjob,needs.<job>is thereforeundefined, so the value resolves to empty even though theagentjob received the intended value.The
agentjob (fixed in #30239), theactivationjob (via the #30790on.permissions: {}workaround), and theconclusionjob all correctly gain the custom job in theirneeds. Only thedetectionjob is left out, and none of the known workarounds coerce it in.The example below sets
COPILOT_MODELfrom aselect_modeljob that runs earlier in the workflow, but the same happens for anyengine.envvalue built from aneeds.<job>.outputs.*expression.repro-detection-needs-bug.md
safe-outputsauto-enables threat detection, so adetectionjob is generated. Theon.permissions: {}+needs: [pre_activation]workaround from #30790 is applied so thatactivation,agent, andconclusionall correctly gainselect_model, isolatingdetectionas the only job that does not.Expected Result
The
detectionjob gainsselect_modelin itsneeds, matchingagentandconclusion:Actual Result
detectionis missingselect_model, even though its engine step still setsCOPILOT_MODELfromneeds.select_model:For comparison, every other job that consumes the expression gains
select_model:No known workaround
needs.<job>.in the markdown body) does not add the job to thedetectionjob'sneeds.on.permissions: {}to produce apre_activationjob, plusneeds: [pre_activation]on the custom job) fixesactivation/agent/conclusionbut notdetection.Impact
Any workflow that derives an
engine.envvalue from an earlier job's output--for example, selecting the model dynamically--gets that value silently blanked in the threat-detection job. Detection then runs with a different engine configuration than the agent, and there is no compile-time error or warning because aneeds.<job>reference outside the job'sneedslist resolves to empty rather than failing.Suggested Fix
Extend the
engine.envneeds-expression scan added in #30239 (and applied toactivationper #30790) so that thedetectionjob'sneedslist is populated from the sameneeds.<job>.outputs.*references. Thedetectionjob inherits theengine.envmapping, so it should inherit the corresponding job dependencies as well.