Skip to content
Docs

Run AI prompts in your GitHub Actions with the Vercel AI Action

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.

Ben SabicContent Engineer

You can call the 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.

Copy link to headingPrerequisites

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

Copy link to headingHow it works

The action wraps the AI SDK 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.

Copy link to headingSteps

Copy link to heading1. Create an AI Gateway API key

Open the AI Gateway API Keys page in your Vercel dashboard and click Create API Key. Copy the key to your clipboard before closing the dialog.

Copy link to heading2. 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.

Copy link to heading3. 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:

.github/workflows/ai.yml
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 and replace openai/gpt-5.5 with any supported identifier.

Copy link to heading4. 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:

.github/workflows/ai.yml
- 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 }}

Copy link to heading5. 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:

.github/workflows/ai.yml
- 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:

.github/workflows/ai.yml
- 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.

Copy link to headingHandle 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.

Copy link to headingWhy 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.

Copy link to headingThe 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:

.github/workflows/ai.yml
- 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 }}

Copy link to headingRules 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.

Copy link to headingInputs and outputs reference

The action accepts the following inputs:

InputRequiredDescription
promptYesThe input prompt sent to the model.
api-keyYesYour AI Gateway API key. Always pass this from secrets, never inline.
modelYesA model identifier from the models directory, for example openai/gpt-5.5.
systemNoA system message that sets the model's role or instructions.
schemaNoA 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:

OutputAvailable whenDescription
textAlwaysThe model's response. When schema is set, this contains the JSON as a string.
jsonschema is setThe parsed object that conforms to your schema.

Copy link to headingCommon use cases

These patterns combine the basic steps above into complete workflows.

Copy link to headingTriage 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.

Copy link to headingSummarize 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 file in a follow-up commit.

Copy link to headingLight 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.

Copy link to headingDetect 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.

Copy link to headingTroubleshooting

Copy link to headingThe 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_GATEWAY_API_KEY }}. If the secret was rotated in the Vercel dashboard, update it in GitHub as well.

Copy link to headingThe 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.

Copy link to headingShell 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 section above.

Related documentation

More AI Gateway guides