Start Netlify Agent Runners agent runs from GitHub issues and pull requests. Just mention @netlify and the selected agent builds your site.
Runner execution uses the published nax-agent-runner-sdk and lands changes as an open pull request; this action never merges automatically.
Install netlify-coding on your repository. This lets the agent create commits and pull requests.
Link your repo to a Netlify site. Note the Site ID from Site configuration > General.
netlify init
Go to Settings > Secrets and variables > Actions and add:
| Secret | Description |
|---|---|
NETLIFY_AUTH_TOKEN | Your Netlify personal access token |
NETLIFY_SITE_ID | Your Netlify site ID |
Create .github/workflows/netlify-agents.yml:
name: Netlify Agent Runners
on:
workflow_dispatch:
inputs:
trigger_text:
description: 'Prompt for the agent run'
required: true
type: string
default: '@netlify'
actor:
description: 'Actor triggering the agent'
required: true
type: string
agent:
description: 'Agent to use'
required: false
type: choice
options:
- codex
- claude
- gemini
default: 'codex'
pull_request_target:
types: [opened, reopened]
pull_request_review_comment:
types: [created]
pull_request_review:
types: [submitted, edited]
issues:
types: [opened, assigned, edited]
issue_comment:
types: [created, edited]
concurrency:
group: ${{ (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') && github.event.comment.body == '@netlify stop' && format('netlify-stop-{0}', github.run_id) || format('netlify-{0}-{1}', github.repository, github.event.pull_request.number || github.event.issue.number || inputs.recover_thread || github.run_id) }}
cancel-in-progress: false
jobs:
netlify-agent:
# Skip bot senders early to avoid burning Actions minutes
if: >-
github.event_name == 'workflow_dispatch' ||
(
github.event.sender.login != 'github-actions[bot]' &&
github.event.sender.login != 'netlify-coding[bot]' &&
github.event.sender.login != 'netlify[bot]'
)
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: write
pull-requests: write
issues: write
steps:
- uses: netlify-labs/agent-runner-action@v1
with:
netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
netlify-site-id: ${{ secrets.NETLIFY_SITE_ID }}
# preflight-only: 'false' # Validate setup and skip agent execution
Create an issue with @netlify in the body:
@netlify Build a landing page for a coffee shop with a menu and contact form
Specify claude, codex, gemini, or opencode after @netlify:
@netlify claude Add a dark mode toggle
@netlify codex Make the hero section responsive
@netlify gemini Add a testimonials carousel
Default agent is codex. You can change the default with the default-agent input.
After @netlify, name an agent, a model, and an effort level, in that order. Each is optional, and a model picks its own agent:
@netlify fable high Refactor the checkout flow
@netlify claude fable high Refactor the checkout flow
@netlify-fable high Refactor the checkout flow
@netlify codex sol medium Add retry logic
@netlify claude high Fix the header
Claude models: fable, opus, opus-4.8, sonnet, haiku. Codex: codex sol, codex terra, codex luna, codex mini. Gemini: gemini pro, gemini flash, gemini flash-lite. OpenCode: kimi, kimi-code, glm, deepseek, deepseek-flash, grok, minimax. Exact model IDs and model:<id> also work.
Effort is low, medium, or high (some OpenCode models also take max; Kimi K2.7 Code and MiniMax M3 take none), placed after an agent or model and followed by a space, :, ,, or the end of the line. effort:<level> anywhere on the @netlify line takes precedence, and effort:auto forces Auto. Anything left out is chosen by the backend (Auto) unless you set default-model-id or default-effort.
After the first run creates a PR, add follow-up @netlify comments on the PR to iterate. The agent continues from where it left off.
dry-run and preflight-only are different controls:
dry-run: 'true' still starts an agent run, but skips commit and PR creation.preflight-only: 'true' validates setup and exits before any agent run is created or resumed.true, preflight-only behavior wins and no agent run starts.- uses: netlify-labs/agent-runner-action@v1
with:
netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
netlify-site-id: ${{ secrets.NETLIFY_SITE_ID }}
preflight-only: 'true' # setup validation only
- uses: netlify-labs/agent-runner-action@v1
with:
netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
netlify-site-id: ${{ secrets.NETLIFY_SITE_ID }}
dry-run: 'true' # run agent preview, skip commits/PR
Common typos are recognized: @nelify, @netlfy, @netify, @netlif, @netfly. Aliases like @netlify-agent and @netlify-ai also work.
| Input | Required | Default | Description |
|---|---|---|---|
netlify-auth-token | Yes | — | Netlify personal access token |
netlify-site-id | Yes | — | Netlify site ID |
netlify-filter | No | '' | Deprecated compatibility input. SDK dispatch uses the exact netlify-site-id. |
github-token | No | github.token | GitHub token for API calls |
allowed-users | No | '' | Comma-separated usernames allowed to trigger |
default-agent | No | codex | Default agent (claude, codex, gemini, opencode) |
default-model | No | codex | Backward-compatible alias for default-agent |
default-model-id | No | '' | Default model ID or alias (e.g. claude-fable-5, fable). Empty or auto lets the backend choose |
default-effort | No | '' | Default effort level (low, medium, high). Empty or auto lets the backend choose |
manage-labels | No | false | Auto-create and apply labels on runs |
protected-paths | No | default | Files to flag for review when a run changes them (globs; default = built-in list, none disables, !pattern excludes) |
flag-new-top-level | No | true | Also flag files added at the repository root and new top-level folders |
protected-paths-action | No | comment | comment (always on), plus optionally label and/or draft |
scope-instructions | No | default | Guidance appended to every agent prompt; none disables |
simulate-orphan | No | false | Test-only (canary): exit after the run checkpoint is written |
operation | No | trigger | trigger or recover-scan (scheduled recovery) |
recover-workflow | No | netlify-agents.yml | For recover-scan: the main workflow file to dispatch |
recover-lookback-hours | No | 48 | For recover-scan: only check recently updated threads |
recover-max-items | No | 50 | For recover-scan: maximum threads to check |
attach-pr-diff | No | true | On PRs, attach the full diff against the base branch to the agent session (.netlify/assets/); the runner can't git diff against the base itself |
dry-run | No | false | Start an agent run but skip commit/PR creation |
preflight-only | No | false | Validate setup and exit without creating/resuming an agent run |
job-timeout-minutes | No | '' | The job's timeout-minutes; the agent limit is shortened to fit inside it |
timeout-minutes | No | 10 | Max minutes to wait for agent |
netlify-cli-version | No | 24.8.1 | Netlify CLI version to install |
debug | No | false | Enable debug API logging |
timezone | No | America/Los_Angeles | Timezone used for date/time rendering in comments |
Use these in subsequent workflow steps for custom automation:
| Output | Description |
|---|---|
agent-id | Agent run ID |
outcome | success, failure, or timeout |
agent-result | Agent result summary text |
agent-pr-url | Pull request URL (if created) |
agent-deploy-url | Deploy preview URL |
agent | Agent that was used |
model | Backward-compatible alias for agent |
model-id | Model ID that was requested (empty when the backend chose Auto) |
effort | Effort level that was requested (empty when the backend chose Auto) |
trigger-text | Cleaned trigger prompt |
is-pr | true / false |
issue-number | Issue or PR number |
is-dry-run | Whether the run used preview mode (true / false) |
preflight-ok | Whether preflight validation passed (true / false) |
preflight-json | Serialized preflight result payload (ok, checks, warnings, failures) |
preflight-summary | Human-readable preflight status summary |
should-continue | Whether execution should continue into agent runtime |
failure-category | Preflight/runtime taxonomy category when available |
failure-stage | Preflight/runtime failure stage when available |
scope-flags | JSON array of files the scope check flagged |
scope-flag-count | Number of files the scope check flagged |
agent-error | Sanitized runtime error summary emitted by agent orchestration |
steps:
- uses: netlify-labs/agent-runner-action@v1
id: agent
with:
netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
netlify-site-id: ${{ secrets.NETLIFY_SITE_ID }}
- name: Run tests on agent PR
if: steps.agent.outputs.outcome == 'success'
run: echo "PR created at ${{ steps.agent.outputs.agent-pr-url }}"
Ask mode answers a question about the code in a comment, without changing files or opening a PR. Use @netlify-ask, @netlify ask: (the colon is required), or mode:ask right after the mention: for example @netlify-ask fable high how are webhooks retried?. A plain "ask" isn't enough, because @netlify ask the user to confirm… is a build request. The answer appears as a result comment marked 💬 Answered. On a PR, the agent reads the PR branch, and a later @netlify request continues with the same context.
After a successful run, the action checks which files that run changed and adds a ⚠️ Review these changes table to the result comment (and to the PR, for runs started from an issue) when it touched workflows, netlify.toml, root *.toml files, lockfiles, AGENTS.md/CLAUDE.md, .env files, new root files, or new top-level folders. Files the request names are listed as requested instead. It never blocks a run.
Configure it with protected-paths (globs; a pattern without / matches only at the root; default, none, and !pattern are supported) and escalate with protected-paths-action: comment,label,draft. By default, scope-instructions also tells agents to report unrelated build or deploy failures instead of working around them.
When a run starts, the status comment links to the live agent run and stores an encrypted checkpoint. The checkpoint is sealed with a key derived from your Netlify token, bound to this thread and runner, and never contains the prompt or site ID in plain text. Cancelling the workflow stops the agent run. Set job-timeout-minutes to your job's timeout-minutes so the agent's own limit fits inside the job. Transient API errors while waiting are retried until the run's time limit.
To stop a run, add a comment containing only @netlify stop. The action stops the agent right away, and the run's comments say "Stopped by @you" instead of "failed". This needs the stop-aware concurrency group from the current templates, so the stop doesn't wait behind the run it stops.
Releases are tagged vX.Y.Z, and v1 always points at the latest v1.Y.Z release. Use @v1 to follow minor and patch releases automatically, or pin a release commit with a version comment (@<sha> # v1.2.0) and let Dependabot's github-actions updates propose upgrades.
Breaking changes (removed or renamed inputs and outputs, incompatible comment markers, or mention syntax that selects something different) only ship in a new major version. Release notes call out behavior changes and any workflow-file updates needed to use new features.
@netlify in an issue, PR, or commentallowed-users input can further restrict accessGo to Settings > Secrets in your repo and add both secrets. Create a token here. Find your Site ID in the Netlify dashboard under Site configuration > General.
Use preflight-summary and preflight-json outputs to identify failing checks. Common fixes: token/site ID mismatch, invalid default agent, non-positive timeout, or missing workflow permissions.
The NETLIFY_SITE_ID secret points to a site that doesn't exist or that your token can't access. Verify the site ID in the Netlify dashboard and regenerate your token if needed.
The default timeout is 10 minutes. For complex prompts, increase it with timeout-minutes: '15'. You can also break large tasks into smaller follow-up prompts.
This is expected. dry-run skips commit and PR creation only. Use preflight-only: 'true' when you need validation with no agent run.
The selected agent is temporarily unavailable. Try a different agent: @netlify claude, @netlify codex, or @netlify gemini.
Add a job-level if: guard to skip bot senders. The example workflow includes this by default. See the setup section.
Update the action dependencies in action.yml to their latest versions (v5/v6/v8). The published action already uses Node 24-compatible versions.