Skip to content
Latchkey

GitHub Actions runs-on From a Matrix Expression Not Resolving

A dynamic runs-on driven by a matrix value or expression fails to match a runner because the expression resolves to the wrong shape - a string where a label array was needed, or an unresolved value.

What this error means

A job using runs-on: ${{ matrix.os }} (or a fromJSON expression) never matches a runner or errors at start, because the resolved value is not a valid label or label array.

.github/workflows/ci.yml
strategy:
  matrix:
    runner: ['["self-hosted","gpu"]']
runs-on: ${{ matrix.runner }}   # a string, not an array of labels

Common causes

Expression resolves to the wrong type

runs-on accepts a string, a label array, or a group object. An expression that yields a JSON string instead of an array does not become a label set.

Matrix value missing for some legs

If the matrix key feeding runs-on is absent in some combinations (e.g. only added via include), those legs get an empty runs-on and cannot match.

How to fix it

Resolve to a real label or array

Use a plain string for a single label, or fromJSON to produce a real label array.

.github/workflows/ci.yml
strategy:
  matrix:
    os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}          # single label string
# for an array of labels:
runs-on: ${{ fromJSON('["self-hosted","gpu"]') }}

Ensure the matrix key is always present

  1. Define the runs-on key in the base matrix so every leg has it.
  2. Use fromJSON when you need an actual array of labels from a string.
  3. Echo the resolved value in a debug step to confirm its shape.

How to prevent it

  • Drive runs-on from a matrix key present in every combination.
  • Use fromJSON to turn a JSON string into a real label array.
  • Verify the resolved runs-on shape before relying on it.

Frequently asked questions

What causes "runs-on expression"?
runs-on accepts a string, a label array, or a group object. An expression that yields a JSON string instead of an array does not become a label set.
How do I fix runs-on expression?
Use a plain string for a single label, or fromJSON to produce a real label array.
Can Latchkey fix this automatically?
Yes. Latchkey runs your GitHub Actions on managed runners that detect this failure, apply the fix, and retry the job automatically - self-healing is on by default.

Related guides

References

Latchkey auto-heals failures like this one - detected, fixed, and retried without you. Start free → 30-day trial · No credit card