Explainer

copilot-setup-steps.yml Explained: Prepare GitHub Copilot Coding Agent for Python and Node.js

What copilot-setup-steps.yml does, the one job it must contain, which keys count, and complete Python and Node.js examples with least-privilege permissions.

copilot-setup-steps.yml Explained: Prepare GitHub Copilot Coding Agent for Python and Node.js/blog/copilot-setup-steps-yml/

Short answer. copilot-setup-steps.yml is a GitHub Actions workflow file at .github/workflows/copilot-setup-steps.yml that runs before GitHub Copilot’s cloud agent starts a task, so the agent begins with your dependencies installed instead of discovering them by trial and error. It must contain exactly one job, named copilot-setup-steps; it only takes effect once it is on the default branch; and of everything a workflow job can say, the agent honours six keys — steps, permissions, runs-on, services, snapshot and timeout-minutes (max 59) — and ignores the rest. Two complete files are below, one for a Python/FastAPI service and one for a Node.js/TypeScript project. What was and was not validated about them is stated plainly.

GitHub renamed the feature this year: what the documentation called “Copilot coding agent” is now “Copilot cloud agent”. The file name did not change, and this article uses both terms. The cloud agent lesson explains the environment the file configures; this page is only about the file.

Facts were read from GitHub’s documentation on 18 September 2026 and are quoted where they matter. The examples were validated as described below, not executed by the agent — see the validation section before trusting them.

What the file is for

The cloud agent works in “its own ephemeral development environment, powered by GitHub Actions, where it can explore your code, make changes, execute automated tests and linters and more.” Without help, it has to install your project’s dependencies itself. GitHub’s own assessment of how that goes: “Copilot can discover and install these dependencies itself via a process of trial and error, but this can be slow and unreliable, given the non-deterministic nature of large language models, and in some cases, it may be completely unable to download these dependencies — for example, if they are private.”

The setup-steps file makes that deterministic. Its job runs first; then the agent starts with whatever the job left behind — a populated virtual environment, a node_modules, a compiled toolchain. The practical effect is that the agent can run your tests before it pushes, which is the difference between a pull request that passes CI and one that does not.

The same file customises Copilot code review’s environment too: “By default, Copilot code review reuses your copilot-setup-steps.yml file”, unless a dedicated .github/workflows/copilot-code-review.yml exists, in which case that one wins for review.

Where it lives, and the rule people miss

Three facts decide whether the file is picked up at all.

  1. Path. .github/workflows/copilot-setup-steps.yml. Not .github/copilot-setup-steps.yml, not workflows/setup.yml.
  2. Job name. The file “must contain a single copilot-setup-steps job”. GitHub’s comment in its own example is blunter: “The job MUST be called copilot-setup-steps or it will not be picked up by Copilot.” A second job in the file is not run by the agent.
  3. Default branch. “The copilot-setup-steps.yml workflow won’t trigger unless it’s present on your default branch.” A file that exists only on the feature branch where you are testing it does nothing for the agent until it is merged. This is the one that produces “I added the file and nothing changed.”

What you can set, and what is ignored

Inside the job, only these are honoured: steps, permissions, runs-on, services, snapshot, timeout-minutes (maximum 59). “If you try to customize other settings, your changes will be ignored.” So env at job level, container, strategy, concurrency, outputs and needs are silently dropped. If you need an environment variable for a step, set it on the step with env:, or export it inside a run: block.

One more override: any fetch-depth on actions/checkout “will be overridden to allow the agent to rollback commits upon request”. Set it if you like; it will not be what runs.

Two behaviours to plan around:

  • You do not have to check out the code. “If you do not check out your code, Copilot will do this for you.” You only need actions/checkout — and therefore contents: read — if a later step needs the repository present, which installing dependencies from a lockfile does.
  • A failing step does not stop the agent. “If any setup step fails by returning a non-zero exit code, Copilot will skip the remaining setup steps and begin working with the current state of its development environment.” A broken step degrades the environment quietly; the agent still runs and still opens a pull request. Watch the session log for it.

Permissions and least privilege

GitHub’s example carries the guidance in a comment: “Set the permissions to the lowest permissions possible needed for your steps. Copilot will be given its own token for its operations.” The setup job’s token is for the setup job — cloning, fetching packages — and nothing else. In practice:

permissions:
  contents: read

is the whole of what a dependency-install job needs, and it is only needed because actions/checkout reads the repository. A job that installs a language toolchain and nothing from the repository can declare permissions: {}.

Do not grant contents: write, pull-requests: write or packages: write here on the theory that the agent might need them. The agent’s own token is separate and scoped by GitHub; widening the setup job widens only what a compromised setup step could do. The GitHub Actions lesson’s permissions section is the general rule; the setup-steps job is the case where it is easiest to follow.

Secrets are configured separately, as “Agents” secrets and variables in the repository’s Copilot settings, not as workflow secrets — so a private package registry token belongs there, referenced from a step, never written into the file.

Example 1: Python/FastAPI

A service with pyproject.toml, an optional dev dependency group (pytest, ruff, mypy), and a lockfile produced by pip-compile or uv. Adjust the Python version to your requires-python.

name: "Copilot Setup Steps"

# Run on changes to this file so it validates like any other workflow, and
# on demand from the Actions tab. Nothing else: this is not CI.
on:
  workflow_dispatch:
  push:
    paths:
      - .github/workflows/copilot-setup-steps.yml
  pull_request:
    paths:
      - .github/workflows/copilot-setup-steps.yml

jobs:
  # The job MUST be named copilot-setup-steps or the agent ignores the file.
  copilot-setup-steps:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    # Only what the steps below need. Copilot's own work uses its own token.
    permissions:
      contents: read

    steps:
      - name: Check out the repository
        uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: |
            pyproject.toml
            requirements*.txt

      - name: Install the project and its dev dependencies
        run: |
          python -m pip install --upgrade pip
          if [ -f requirements-dev.txt ]; then
            pip install -r requirements-dev.txt
          fi
          pip install -e ".[dev]"

      - name: Prove the environment works
        # If this fails, the agent starts anyway — but the session log will
        # show it, and that is where to look when the agent "cannot run tests".
        run: |
          python -c "import fastapi, pydantic; print('fastapi', fastapi.__version__)"
          ruff --version
          pytest --version

Why each part is the way it is:

  • on: is narrow on purpose. The file is run as a normal workflow when it changes, “so you can see if it runs successfully”, and GitHub notes that “you wouldn’t typically run on both push and pull_request” for a real project — here both are path-filtered to the file itself, so it runs on the pull request that edits it and once more when that lands. It is not your test suite.
  • cache: pip keeps repeat runs fast. The agent’s environment is ephemeral; the cache is the one thing that persists between sessions.
  • The last step is a smoke test, not a test run. Its purpose is to make a broken install visible in the session log. Running the whole test suite here would spend minutes before every agent session on work the agent will do again.

Example 2: Node.js/TypeScript

A project with package-lock.json, TypeScript, ESLint and a test script. Adjust the Node version to your engines field.

name: "Copilot Setup Steps"

on:
  workflow_dispatch:
  push:
    paths:
      - .github/workflows/copilot-setup-steps.yml
  pull_request:
    paths:
      - .github/workflows/copilot-setup-steps.yml

jobs:
  copilot-setup-steps:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    permissions:
      contents: read

    steps:
      - name: Check out the repository
        uses: actions/checkout@v6

      - name: Set up Node.js
        uses: actions/setup-node@v7
        with:
          node-version: "22"
          cache: npm

      - name: Install dependencies from the lockfile
        # npm ci, not npm install: the lockfile is the contract, and the agent
        # should not be able to drift it by installing.
        run: npm ci

      - name: Build once so type errors surface here, not in the agent's first attempt
        run: npx tsc --noEmit

      - name: Prove the toolchain works
        run: |
          node --version
          npx eslint --version
          npm run test -- --version >/dev/null 2>&1 || echo "no test runner version flag; that is fine"

Notes:

  • npm ci over npm install. ci refuses to run if the lockfile and package.json disagree, which is exactly the failure you want surfaced before the agent starts editing.
  • The tsc --noEmit step is optional but cheap. A repository that does not type-check on main is one where the agent will spend its first minutes discovering that; better the setup log says so.
  • Monorepos with workspaces. npm ci at the root installs every workspace. If the agent only ever works in one package, npm ci --workspace packages/api is faster and narrower. The monorepo instructions article covers the instruction-file side of the same repository.

Validation: how to know it is being used

Three checks, in order, each of which the documentation describes.

  1. The workflow ran on its own. After you commit the file, it “will automatically be run as a normal GitHub Actions workflow when changes are made … This will show alongside other checks in a pull request where you create or modify the file.” A red check here is a setup problem you can fix before an agent ever sees it.
  2. Run it by hand from the Actions tab after merging: “you can manually run the workflow from the repository’s Actions tab at any time to check that everything works as expected.” That is what workflow_dispatch in the examples is for.
  3. Read the session log. “When Copilot starts work, your setup steps will be run, and updates will show in the session logs.” Assign the agent a small task, open the session from the pull request timeline, and confirm the setup steps appear at the top and each exits zero. If they are absent, the file was not picked up — re-check the path, the job name, and the default branch.

Debugging failures

The steps do not appear in the session log at all. Path, job name, default branch — in that order. Then confirm the cloud agent is actually enabled for the repository; GitHub’s troubleshooting page points at github.com/settings/copilot/features for individuals and at the organization’s Copilot settings otherwise.

The steps appear and one fails. The agent continues with whatever state the failing step left. Reproduce by running the workflow from the Actions tab — same runner, same steps — and read the failing step’s output there, where it is easier to see than in the session log.

A dependency cannot be fetched. The agent’s environment sits behind a firewall by default: “Limiting access to the internet helps to manage data exfiltration risks.” A blocked request adds a warning to the pull request body or comment “showing the blocked address and the command that tried to make the request”. Public registries are allowed; a private registry or an internal mirror needs the firewall’s allow list extended, or a self-hosted runner. Do not disable the firewall to make one package install.

The agent’s pull request fails CI even though setup passed. Two different things. Setup steps prepare the environment; whether the agent uses it to run tests before pushing depends on what you told it. GitHub’s troubleshooting page: it “is most likely to do this if given clear instructions on what to do. The best way to do this is with a .github/copilot-instructions.md file.” Tell it, in that file, which command is the definition of done. The Setup Kit generates an instruction file whose “before completing a task” section says exactly that.

The workflow does not run when the agent pushes. Separate again: “GitHub Actions workflows will not run automatically when Copilot pushes changes to a pull request.” A person with write access clicks Approve and run workflows in the merge box. This is a safety default, not a setup-steps problem.

Security considerations

The setup job runs arbitrary steps you wrote, on a GitHub-hosted runner, with the permissions you granted, before an AI agent starts editing your repository. Three consequences.

Pin what you run. actions/checkout@v6 and actions/setup-node@v7 are major-version tags in the examples for readability; the Actions lesson on pinning explains why a commit SHA is the stricter choice and how to keep it updated. A run: step that pipes curl into sh is exactly as dangerous here as anywhere else.

Keep permissions at contents: read. The point of a separate setup token is that a supply-chain compromise in a setup action cannot write to your repository. Granting more here throws that away.

Treat the file as configuration, not documentation. A pull request that edits copilot-setup-steps.yml is proposing to change what runs before every agent session. Review it as you would a change to CI, and note that it runs on that pull request when path-filtered triggers are present — for outside contributors on public repositories, the forked pull request rules apply.

Self-hosted runners change the picture: the built-in firewall “is not compatible with self-hosted runners” and must be disabled, so your own network controls become the only boundary. GitHub recommends “ephemeral, single-use runners that are not reused for multiple jobs”.

Common mistakes

MistakeWhat happensFix
File on a feature branch onlyNothing; the agent never sees itMerge to the default branch
Job named setup or buildIgnoredRename to copilot-setup-steps
Two jobs in the fileOnly the correctly named one runsOne job; put everything in its steps
Job-level env: or container:Silently ignoredUse step-level env:, or install in a step
timeout-minutes: 90Capped; the job is limited to 59Trim the steps; cache
npm install instead of npm ciLockfile drift, slower runsnpm ci
Running the full test suite in setupMinutes spent before every session on work the agent repeatsA smoke check only
permissions: write-all “to be safe”The opposite of safecontents: read
Expecting the agent to run tests because they are installedIt runs them when instructedSay so in copilot-instructions.md

What was validated

Stated exactly, because “tested” means something.

Statically validated, in this repository’s build: both examples parse as YAML; each contains exactly one job named copilot-setup-steps; every key on that job is one of the six the documentation says is honoured; timeout-minutes is at or below 59; permissions grants only contents: read; every uses: is a pinned major version of a first-party actions/* action. A test in the site’s suite (tests/setup-steps-yaml.test.ts) re-runs those checks, so an edit to this page that breaks a rule fails the test run.

Not executed: neither file has been run by GitHub Actions or by the Copilot cloud agent from this environment, which has no GitHub Actions and no Copilot access. The run: steps are ordinary shell and were not run here against a real project. Before relying on either file, run it yourself from the Actions tab (validation step 2) and read one session log (step 3).

Frequently asked questions

Does it replace my CI workflow? No. It prepares the agent’s environment; your CI still runs on the agent’s pull request, after a person approves the workflow run.

Can I use a different runner? Yes — runs-on is honoured, for larger GitHub-hosted runners or a self-hosted scale set. Only Ubuntu x64 and Windows 64-bit are supported; “Runners with macOS or other operating systems are not supported.”

Can I run Docker services for integration tests? services is one of the honoured keys, so a Postgres or Redis service container can be declared on the job as in any workflow.

Does the agent see my job-level environment variables? No; job-level env is not among the honoured keys. Use Copilot’s Agents variables and secrets in the repository settings for values the agent should have, and step-level env: for values a setup step needs.

What if I need Windows? Supported, but the integrated firewall “is not compatible with Windows”, so GitHub recommends self-hosted runners or larger runners with Azure private networking where you control the network yourself.

Does the same file configure code review? By default, yes. Add .github/workflows/copilot-code-review.yml if review needs a different environment; when it exists it is used for review instead.

Recap and next step

One file, one job with the exact name, on the default branch; six honoured keys; contents: read; install from the lockfile; a smoke check at the end; then confirm it in a session log. Pair it with an instruction file that names the command the agent must run before it is done — the file makes the tests possible, the instructions make them happen.

The Copilot Project Setup Kit generates that instruction file for a Python/FastAPI or TypeScript/Node.js repository in your browser. The cloud agent lesson explains the environment this file configures, the coding agent tutorial covers preparing a repository for delegation, and the Python and TypeScript lessons cover the conventions the agent will be held to. If you would rather start from maintained files — an instruction file per stack, with the reasoning kept — that is what The Copilot Stack Pro is for; the Terraform one is public in full.

Sources

Every version-sensitive claim on this page was checked against first-party documentation. Only sources actually used are listed.

Primary sources

Additional references

Go deeper in the Academy

  • GitHub Copilot Cloud Agent ExplainedWhere it runs, what it can reach, and how a GitHub Actions-powered agent differs from the one editing your local working tree.
  • GitHub Copilot Coding Agent TutorialDelegating a real task end to end: writing an issue an agent can act on, what it does with it, and reviewing the pull request that comes back.
  • GitHub Copilot for GitHub ActionsLeast-privilege permissions, SHA-pinned actions, OIDC and the script-injection vector in a pull request title — the one thing actionlint does catch.
  • GitHub Copilot for PythonType hints as context, virtual environments, pytest generation, Ruff and mypy as the review layer — and the mutable-default and unsafe-subprocess patterns to watch for.
  • GitHub Copilot for TypeScriptWhat types change: better suggestions in, a compiler that rejects wrong output, and the assertions and `any` casts Copilot reaches for when it cannot satisfy them.