---
title: Run AI prompts in your GitHub Actions with the Vercel AI Action
description: Use the vercel/ai-action@v2 GitHub Action to call the AI Gateway from a workflow. Generate text or structured JSON, set system messages, and handle untrusted input safely.
url: /kb/guide/run-ai-prompts-in-your-github-actions-with-the-vercel-ai-action
canonical_url: "https://vercel.com/kb/guide/run-ai-prompts-in-your-github-actions-with-the-vercel-ai-action"
published: 2026-05-25
last_updated: 2026-05-26
authors: Ben Sabic
related:
  - /docs/ai-gateway
  - /docs/ai-gateway/authentication
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

You can call the [AI Gateway](https://vercel.com/ai-gateway) directly from a GitHub Actions workflow step with `vercel/ai-action@v2`, generating text or structured JSON without writing a custom script or maintaining provider SDKs. You pass a prompt, pick a model, and read the result from the step's outputs as `text` or, when you provide a JSON Schema, as a parsed `json` object. Use it to triage issues, draft release notes, moderate comments, or run a lightweight code review on pull requests.

This guide walks you through running a basic text prompt, steering the model with a system message, and generating structured JSON that conforms to a schema. You'll also learn how to handle untrusted user content like issue bodies and PR descriptions safely, since prompt injection is a real risk when an AI step's output flows into a `run:` block.

## Prerequisites

Before you begin, make sure you have:

- A Vercel account with AI Gateway access
  
- A GitHub repository where you can add secrets and workflows
  
- Permission to edit `.github/workflows/` in that repository
  

## How it works

The action wraps the [AI SDK](https://ai-sdk.dev/) and routes every request through the AI Gateway, so a single `api-key` gives you access to models from 40+ providers. When you pass a `schema`, the action calls `generateObject` and returns a parsed `json` output. Without a schema, it calls `generateText` and returns the response as `text`. Either way, the result is available to later steps through `steps.<id>.outputs.text` or `steps.<id>.outputs.json`.

## Steps

### 1\. Create an AI Gateway API key

Open the [AI Gateway API Keys page](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%2Fapi-keys) in your Vercel dashboard and click **Create API Key**. Copy the key to your clipboard before closing the dialog.

### 2\. Add the key as a GitHub secret

In your GitHub repository, navigate to **Settings** > **Secrets and variables** > **Actions**, then click **New repository secret**. Name the secret `AI_GATEWAY_API_KEY` and paste the key value.

### 3\. Add a basic workflow

Create `.github/workflows/ai.yml` with a single `vercel/ai-action@v2` step. This workflow runs on every push to `main` and prints the model's response:

```yaml
name: Basic AI prompt
on:
  push:
    branches:
      - main

jobs:
  generate-text:
    runs-on: ubuntu-latest
    steps:
      - uses: vercel/ai-action@v2
        id: prompt
        with:
          prompt: 'Why is the sky blue?'
          model: 'openai/gpt-5.5'
          api-key: ${{ secrets.AI_GATEWAY_API_KEY }}
      - run: echo "$TEXT"
        env:
          TEXT: ${{ steps.prompt.outputs.text }}
```

Browse the full model list on the [models directory](https://vercel.com/ai-gateway/models) and replace `openai/gpt-5.5` with any supported identifier.

### 4\. Add a system message to steer the model

The `system` input sets the model's role and tone. Use it when you want consistent output style across runs, such as for moderation or classification tasks:

```yaml
- uses: vercel/ai-action@v2
  id: prompt
  with:
    system: 'You are a release-notes editor. Reply with one short sentence in past tense.'
    prompt: 'Summarize this commit: Fix race condition in token refresh logic.'
    model: 'openai/gpt-5.5'
    api-key: ${{ secrets.AI_GATEWAY_API_KEY }}
```

### 5\. Generate structured JSON with a schema

Pass a JSON Schema in the `schema` input to get a parsed object on `steps.<id>.outputs.json`. The action calls `generateObject`, which constrains the model to produce output matching your schema. This example classifies a GitHub issue:

```yaml
- uses: vercel/ai-action@v2
  id: classify
  with:
    model: 'openai/gpt-5.5'
    api-key: ${{ secrets.AI_GATEWAY_API_KEY }}
    system: 'You are a triage assistant. Classify GitHub issues by type and urgency.'
    prompt: |
      Issue Title: ${{ github.event.issue.title }}
      Issue Body: ${{ github.event.issue.body }}
    schema: |
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["bug", "feature", "question", "other"]
          },
          "urgent": {
            "type": "boolean",
            "description": "True if the issue blocks the user from a critical task."
          },
          "reason": {
            "type": "string",
            "description": "One-sentence explanation of the classification."
          }
        },
        "required": ["type", "urgent"],
        "additionalProperties": false
      }
```

Read fields from the JSON output with `fromJSON`:

```yaml
- if: fromJSON(steps.classify.outputs.json).urgent
  run: gh issue edit "$NUMBER" --add-label urgent
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    NUMBER: ${{ github.event.issue.number }}
```

**Note:** The example above shows the safe pattern for using AI output in shell commands. See the next section for why this matters.

## Handle untrusted input safely

Issue titles, issue bodies, PR descriptions, and comments are user input. Treating them as trusted in a `run:` block exposes your workflow to prompt injection. An attacker can craft content that manipulates the model into producing shell commands, which then execute with your workflow's permissions.

### Why string interpolation in `run:` is dangerous

GitHub Actions performs variable interpolation in `run:` blocks before the shell sees the script. If you write `echo "${{ steps.classify.outputs.json }}"` and the model returns text containing backticks or `$()`, the shell evaluates those substitutions. An attacker who controls the prompt input can therefore execute arbitrary commands inside your runner.

### The safe pattern: use environment variables

Pass any value derived from user input or model output through the `env:` block, then reference it as a shell variable. The shell receives the value as a literal string and never re-evaluates it:

```yaml
- name: Apply label
  if: fromJSON(steps.classify.outputs.json).urgent
  run: |
    gh issue edit "$NUMBER" --add-label urgent
    echo "Reason: $REASON"
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    NUMBER: ${{ github.event.issue.number }}
    REASON: ${{ fromJSON(steps.classify.outputs.json).reason }}
```

### Rules of thumb

Apply these rules whenever a workflow handles user-controlled content or AI output:

- **Never interpolate** `**${{ ... }}**` **expressions directly inside a** `**run:**` **script.** Move every dynamic value into `env:` first.
  
- **Quote shell variables.** Use `"$VAR"` rather than `$VAR` so values with spaces or special characters don't break the command.
  
- **Scope tokens to the minimum.** Use `GITHUB_TOKEN` with the least privilege needed instead of a long-lived personal access token.
  
- **Treat model output as user input.** Even if your prompt comes from a maintainer, the model's response is influenced by anything the model has read, including issue bodies it was asked to summarize.
  

## Inputs and outputs reference

The action accepts the following inputs:

| Input     | Required | Description                                                                                                         |
| --------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| `prompt`  | Yes      | The input prompt sent to the model.                                                                                 |
| `api-key` | Yes      | Your AI Gateway API key. Always pass this from `secrets`, never inline.                                             |
| `model`   | Yes      | A model identifier from the [models directory](https://vercel.com/ai-gateway/models), for example `openai/gpt-5.5`. |
| `system`  | No       | A system message that sets the model's role or instructions.                                                        |
| `schema`  | No       | A JSON Schema (draft 2020-12 or compatible). When set, the action returns structured JSON instead of plain text.    |

The action exposes these outputs on `steps.<id>.outputs`:

| Output | Available when  | Description                                                                     |
| ------ | --------------- | ------------------------------------------------------------------------------- |
| `text` | Always          | The model's response. When `schema` is set, this contains the JSON as a string. |
| `json` | `schema` is set | The parsed object that conforms to your schema.                                 |

## Common use cases

These patterns combine the basic steps above into complete workflows.

### Triage new issues

Classify incoming issues and label the urgent ones. Use a schema so you can branch on a boolean rather than parsing free text.

### Summarize pull requests for release notes

Run the action on `pull_request: closed` events with `merged == true`, ask for a one-line summary, and append the output to a `RELEASE_`[`NOTES.md`](http://NOTES.md) file in a follow-up commit.

### Light PR code review

Pass the PR diff (via `gh pr diff`) as the prompt and ask the model to flag obvious issues. Post the result as a comment with `gh pr comment`. Keep this advisory only. It isn't a replacement for human review.

### Detect duplicate issues

When a new issue opens, fetch the titles of recent open issues and ask the model to identify likely duplicates. Use a schema that returns the issue numbers it considers related.

## Troubleshooting

### The action fails with an authentication error

Check that the `AI_GATEWAY_API_KEY` secret exists in the correct scope (repository, environment, or organization) and that the workflow references it as `${{` [`secrets.AI`](http://secrets.AI)`_GATEWAY_API_KEY }}`. If the secret was rotated in the Vercel dashboard, update it in GitHub as well.

### The action fails or returns an empty `json` output

When `schema` is set, the action uses `generateObject` to constrain the model's output to your schema. If the model can't produce conforming output, the step fails or the `json` output is missing. Simplify the schema, add `description` fields to guide the model toward the right structure, or switch to a stronger model. Models vary widely in how reliably they follow JSON Schema, so a model swap is often the fastest fix.

### Shell variables contain unexpected characters

This usually indicates that a `${{ ... }}` expression was interpolated directly into a `run:` block. Move the value to `env:` and reference it as a shell variable. Review the [Handle untrusted input safely](https://claude.ai/chat/47ed45cf-df09-4916-b368-bd07d755e603#handle-untrusted-input-safely) section above.

## Related resources

- [AI Gateway](https://vercel.com/docs/ai-gateway)
  
- [AI Gateway model catalog](https://vercel.com/ai-gateway/models)
  
- [AI Gateway authentication](https://vercel.com/docs/ai-gateway/authentication)
  
- [AI SDK](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text) [`generateText`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text)
  
- [AI SDK](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-object) [`generateObject`](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-object)
  
- [`vercel/ai-action`](https://github.com/vercel/ai-action) [source and examples](https://github.com/vercel/ai-action)
  
- [Security hardening for GitHub Actions](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)