diff --git a/.github/workflows/gitnexus-skill-evolution.yml b/.github/workflows/gitnexus-skill-evolution.yml index 7c9adda5a..c1b20965c 100644 --- a/.github/workflows/gitnexus-skill-evolution.yml +++ b/.github/workflows/gitnexus-skill-evolution.yml @@ -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 != '' }} diff --git a/gitnexus/test/unit/skill-evolution-workflow.test.ts b/gitnexus/test/unit/skill-evolution-workflow.test.ts index 9549e3ec0..777aee4a2 100644 --- a/gitnexus/test/unit/skill-evolution-workflow.test.ts +++ b/gitnexus/test/unit/skill-evolution-workflow.test.ts @@ -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