fix(ci): publish the evidence path from a step, not a job-level env

`${{ runner.temp }}` does not exist in a job-level `env:` block — the
runner context is only available to steps — so OUT_ROOT would have
resolved to a bare `/wfevolve` at the filesystem root. The sweep would
have failed writing there, and the upload would have pointed at nothing.
actionlint caught it; this repo lints workflows for exactly this reason.

The property that mattered is kept: the path is fixed before anything can
fail, rather than read from the sweep step's outputs — that being the
step whose death is the reason the upload matters. The first step now
publishes it to GITHUB_ENV, which every later step sees, including the
`if: always()` upload after a killed sweep.

The contract test pins the step's position and its exact line, so the
context cannot creep back into the job block.
This commit is contained in:
Gergo Magyar 2026-08-01 18:52:35 +00:00
parent c05c56ffc9
commit d18dbd4143
2 changed files with 16 additions and 3 deletions

View file

@ -142,10 +142,17 @@ jobs:
MODEL: ${{ inputs.model || 'claude-sonnet-5' }}
PROPOSER_MODEL: ${{ inputs.proposer_model || 'claude-opus-4-8' }}
INCLUDE_EXPENSIVE: ${{ inputs.include_expensive && '1' || '' }}
# Fixed, known before the sweep starts: the upload below must not depend
# on a step that may have been killed having reported an output.
OUT_ROOT: ${{ runner.temp }}/wfevolve
steps:
- name: Pin the evidence path before anything can run
# The upload step must not take its path from the sweep step's outputs
# — that is the step whose death is the reason the upload matters. A
# job-level `env:` cannot hold it either (the `runner` context does not
# exist there), so publish it to GITHUB_ENV first: every later step
# sees it, including the `if: always()` upload after a killed sweep.
run: |
set -euo pipefail
echo "OUT_ROOT=${RUNNER_TEMP}/wfevolve" >> "${GITHUB_ENV}"
- name: Require the benchmark auth secret
env:
HAS_TOKEN: ${{ secrets.GITNEXUS_BENCH_AUTH_TOKEN != '' }}

View file

@ -152,6 +152,12 @@ describe('gitnexus skill-evolution workflow contract', () => {
});
it('uploads benchmark evidence unconditionally, on a path fixed before the sweep runs', () => {
// OUT_ROOT is published to GITHUB_ENV by the first step, not held in a
// job-level `env:` — the `runner` context does not exist there, so
// `${{ runner.temp }}/wfevolve` would silently resolve to `/wfevolve`.
const first = evolveJob?.steps?.[0];
expect(first?.name).toBe('Pin the evidence path before anything can run');
expect(first?.run).toContain('echo "OUT_ROOT=${RUNNER_TEMP}/wfevolve" >> "${GITHUB_ENV}"');
const upload = evolveJob?.steps?.find(({ name }) => name === 'Upload benchmark evidence');
// The sweep appends results.jsonl and transcripts as it goes, so a killed
// generation still holds the evidence explaining why — and a path taken