Skip to content

mutation-cargo.yml: Post Setup Rust fails after workflow-src relocation breaks composite action post hook #367

Description

@leynos

Summary

mutation-cargo.yml's "Relocate workflow source" step (added to fix
#343) moves workflow-src/ out of the caller's workspace immediately
after the "Setup Rust" step runs. "Setup Rust" is a local composite
action invoked as uses: ./workflow-src/.github/actions/setup-rust,
and it wraps actions-rust-lang/setup-rust-toolchain, which registers
an automatic post step (Post Setup Rust) to save the cargo cache
when the restore was only a partial (restore-key) match. That post
step runs at the very end of the job — after "Relocate workflow
source" has already moved (not copied) the directory — so the runner
can no longer resolve the composite action's action.yml at its
original path and the whole job fails, even though the actual mutation
run succeeded.

Observed

leynos/wildside-engine, run
https://github.com/leynos/wildside-engine/actions/runs/29676888803
(scheduled, 2026-07-19), job "mutation / mutants (...)":

2026-07-19T06:43:41Z Cache hit for restore-key: Linux-cargo--e4fedeec3d03d6da393d669f34e5377b094a4000d18ff87a2535c2224510ce46
...
2026-07-19T06:43:59Z ##[error]Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '/home/runner/work/wildside-engine/wildside-engine/workflow-src/.github/actions/setup-rust'. Did you forget to run actions/checkout before running your local action?

The "Run mutation testing" and "Upload mutation report" steps both
succeeded and the mutation report was uploaded, but "Post Setup Rust"
failed, so the job (and the whole run) is reported as a failure.

The fault is conditional on the cache restore being a partial match
(restore-key) rather than an exact key hit: an exact hit skips the
save and the post step never touches action.yml, so the run passes.
This explains why adjacent scheduled runs at the same pinned SHA
(e0d9b652b137eb15314fff188f09e1ba18d3cc5b) on 2026-07-18 and
2026-07-20 were green while the 2026-07-19 run failed — the workspace
had drifted enough to miss the exact cache key that day.

Root cause

mutation-cargo.yml (job mutation, steps around lines 195-273):

- name: Setup Rust
  uses: ./workflow-src/.github/actions/setup-rust   # registers a post step

...

- name: Relocate workflow source            # mv workflow-src -> $RUNNER_TEMP
  run: |
    mv "${WORKFLOW_DIR}" "${dest}"

The comment on "Relocate workflow source" already acknowledges it must
run after "Setup Rust" needs the workspace-local action path, but it
overlooks that GitHub Actions runs any registered post steps for
composite/JS actions at job teardown, in reverse order, regardless of
where the main steps sit in the job. Moving (rather than copying, or
leaving a resolvable stand-in behind) workflow-src before teardown
breaks any earlier step whose action registers a post hook needing its
own action.yml.

Impact

Estate-wide, intermittent: any caller of mutation-cargo.yml whose
cargo cache restore is a partial match on a given scheduled run gets a
spurious job failure, even though the mutation testing itself
completed and uploaded correctly. This is confusing for the 14-day
mutation-testing sweep — failures look like real setup breakage but
are a shared-actions teardown bug.

Suggested fix

Pick one:

  • Copy workflow-src to $RUNNER_TEMP instead of moving it, and leave
    a symlink at the original path pointing to the copy. git ls-files-based hygiene checks (the caller-side mitigation already
    recommended in workflow-src checkout pollutes the caller's tree during mutation runs #343) won't see it, and os.walk-style checks that
    don't follow symlinks (the default) also won't descend into it, but
    the runner can still resolve action.yml for any post hooks
    registered against the original path.
  • Or check out workflow-src directly under $RUNNER_TEMP from the
    start (path: ${{ runner.temp }}/workflow-src on the initial
    checkout) rather than checking it out inside the workspace and
    relocating afterwards, if a local uses: path outside the workspace
    is viable for "Setup Rust".

Either way, workflow-src (or something that resolves to its
action.yml files) needs to remain reachable at its original path for
the lifetime of the job, not just until the last explicit step that
references it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghighHigh criticality issuetesting

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions