# job-defaults (/docs/macros/job-defaults)



Define default job-level settings once at the top level; Actio merges them into
every job at compile time and strips both `job-defaults` and `executors` from the
output — zero residual directives.

Both jobs inherit `runs-on` and `timeout-minutes`; `env` is deep-merged so `lint`
ends up with both `CI` and `DEBUG`:

<CodeCompare>
  ```yaml title=".actio.yml"
  name: CI
  on: [push]
  job-defaults:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      CI: "true"
  jobs:
    test:
      steps:
        - run: npm test
    lint:
      env:
        DEBUG: "1"
      steps:
        - run: npm run lint
  ```

  ```yaml title="generated .yml"
  jobs:
    test:
      runs-on: ubuntu-latest
      timeout-minutes: 30
      env:
        CI: "true"
      steps:
        - run: npm test
    lint:
      runs-on: ubuntu-latest
      timeout-minutes: 30
      env:
        CI: "true"
        DEBUG: "1"
      steps:
        - run: npm run lint
  ```
</CodeCompare>

## Merge semantics [#merge-semantics]

| Key                                                              | Behaviour                                                                                          |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `if`                                                             | **AND-combined** — `default && inline`. Never replaced.                                            |
| `permissions`, `concurrency`                                     | **Replace-on-presence** — inline value wins entirely; default only applies when the key is absent. |
| `runs-on`, `timeout-minutes`, `continue-on-error`, `environment` | **Inline wins** — job value takes precedence.                                                      |
| `env`, `container`, `services`, `defaults`                       | **Deep-merged** — job keys overlay the default.                                                    |

`strategy` is intentionally per-job only. Put matrix and `fail-fast` settings on
each job so Actio does not silently turn per-job identity into a workflow-wide
default.

<CodeCompare>
  ```yaml title=".actio.yml"
  name: CI
  on: [push]
  job-defaults:
    if: ${{ github.ref == 'refs/heads/main' }}
    env:
      REGION: us-east-1
  jobs:
    deploy:
      runs-on: ubuntu-latest
      if: ${{ success() }}
      env:
        REGION: eu-west-1
      steps:
        - run: ./deploy.sh
  ```

  ```yaml title="generated .yml"
  jobs:
    deploy:
      runs-on: ubuntu-latest
      if: github.ref == 'refs/heads/main' && success()
      env:
        REGION: eu-west-1
      steps:
        - run: ./deploy.sh
  ```
</CodeCompare>

<Callout title="Reusable-workflow call jobs">
  Jobs with `uses:` only receive the call-compatible subset of defaults: `if`,
  `permissions`, and `concurrency`. Runner keys like `runs-on`, `env`, and
  `timeout-minutes` are silently skipped (an `info` diagnostic is emitted listing
  what was dropped). `executor:` is not supported on call jobs.
</Callout>

## executors [#executors]

Where `job-defaults` applies to every job, **executors** are named runner presets
a job opts into with `executor:` — for the cases where only *some* jobs need a
hardened runner, a container, or a service block. They share the same merge model
as `job-defaults` (inline job keys win) and compose left-to-right when listed.

See the dedicated [executors](/docs/macros/executors) page for the full guide.


## Sitemap

Browse the full documentation: [Markdown sitemap](https://austenstone.github.io/actio/sitemap.md) · [XML sitemap](https://austenstone.github.io/actio/sitemap.xml)