Initializing Enclave...

How to Fix GitHub Actions 'Unexpected value uses' Error on Line 25 (Only run or shell Allowed)

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins

TL;DR

  • What broke: A uses: key was nested inside a run: step block (or placed at the wrong indentation level), which is an illegal YAML structure in GitHub Actions — uses and run are mutually exclusive step-level keys, not nestable properties.
  • How to fix it: Split the offending block into two separate steps entries: one - uses: step and one - run: step, each at the correct sibling indentation under steps:.
  • Fast path: Drop your full main.yml into our Client-Side Sandbox above to auto-refactor this without leaking your repo secrets anywhere.

The Incident (What Does the Error Mean?)

Raw error output:

Workflow run failed: .github/workflows/main.yml
(Line: 25, Col: 14): Unexpected value 'uses'.
Only 'run' or 'shell' are allowed in this context.

GitHub Actions' workflow parser hit a uses: key at column 14 of line 25 — a position where the schema only permits run: or shell: (i.e., properties that belong inside a run-type step). The runner never starts. Every job in this workflow is dead on arrival. Any PR merge gates, deployment pipelines, or scheduled jobs wired to this workflow are fully blocked.


The Attack Vector / Blast Radius

This is a schema-level hard stop — not a warning, not a skipped step. The entire workflow file is rejected at parse time.

Cascading failure chain:

  1. All jobs in the workflow file fail immediately — not just the job containing line 25.
  2. Branch protection rules break — if this workflow is a required status check, every open PR is now unmergeable.
  3. Deployment pipelines stall — any on: push or on: release trigger tied to this file produces zero deployments.
  4. Silent regression window — if this was introduced in a refactor commit, the team may not notice until a hotfix deploy is attempted under pressure.

The root structural misunderstanding: uses and run are mutually exclusive top-level keys of a single step object. A step either calls a pre-built Action (uses) or executes a shell command (run). You cannot mix them in one step, and you cannot nest uses as a sub-key of a run step.


How to Fix It (The Solution)

Basic Fix

The most common trigger for this error is indentation drift — someone added uses: one level too deep, making the parser interpret it as a property of the preceding run: step.

Broken pattern (what the parser sees):

    steps:
      - name: Install dependencies
-       run: npm ci
-         uses: actions/setup-node@v4  # WRONG: 'uses' nested under 'run' step
-         with:
-           node-version: '20'

Fixed pattern:

    steps:
+     - name: Setup Node
+       uses: actions/setup-node@v4   # Correct: standalone step
+       with:
+         node-version: '20'
+     - name: Install dependencies
+       run: npm ci                   # Correct: separate step

Key rule: Every - under steps: is a new step object. uses: and run: must each be the primary key of their own - block, never nested inside each other.


Enterprise Best Practice

In larger monorepos with multiple contributors, this class of error is best caught before it ever reaches the runner.

# .github/workflows/main.yml — Corrected, production-grade structure

    steps:
-     - name: Checkout and build
-       run: git checkout ${{ github.sha }}
-         uses: actions/checkout@v4      # ILLEGAL: mixed step keys
-         with:
-           fetch-depth: 0

+     - name: Checkout repository
+       uses: actions/checkout@v4        # Step 1: Action-based step
+       with:
+         fetch-depth: 0

+     - name: Run build
+       run: make build                  # Step 2: Shell-based step
+       shell: bash

Additional hardening: Pin all uses: references to a full commit SHA, not a mutable tag, to prevent supply-chain injection:

-     - uses: actions/checkout@v4
+     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

Never let a YAML schema violation reach the GitHub runner. Catch it in the developer's editor or in a pre-merge gate.

1. Local validation with actionlint (fastest feedback loop):

# Install
brew install actionlint   # macOS
# or
curl -s https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash | bash

# Run against all workflow files
actionlint .github/workflows/*.yml

actionlint catches uses/run co-location errors, expression syntax bugs, and invalid context references — all statically, before a single runner spins up.

2. VS Code schema binding (catch it while typing):

// .vscode/settings.json
{
  "yaml.schemas": {
    "https://json.schemastore.org/github-workflow.json": ".github/workflows/*.yml"
  }
}

This gives you red-squiggle validation on uses/run conflicts inline.

3. Pre-commit hook:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/rhysd/actionlint
    rev: v1.7.3
    hooks:
      - id: actionlint

4. Required status check gate: Add a separate lightweight lint-workflows job that runs actionlint on PRs. Set it as a required status check in branch protection. A broken workflow YAML can never merge if the linter job must pass first.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →