Model or dataset
actions/ai-inference avatar
actions/ai-inference

actions/ai-inference: running a model inside a GitHub Actions workflow

An action for calling AI models with GitHub Models

520 stars71 forksTypeScriptMIT

At a glance

What is it?
This GitHub Action calls an AI model from a workflow through the Copilot CLI, with prompt files, templated .prompt.yml inputs, system prompt files and a response file output. It suits a team that wants a model call to be a step in CI rather than a separate service, and it is Copilot-only with no other provider supported.
Who is it for?
actions/ai-inference fits a team that wants a model call inside an existing workflow, for example a review step on a pull request, without standing up a service or an API key of its own. It does not fit a pipeline that needs a non-Copilot provider, since copilot is the only supported value of the provider input, and it does not fit a self-hosted runner where the Copilot CLI is not already installed and authenticated.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A workflow step that shells out to the Copilot CLI

actions/ai-inference runs AI inference in workflows using the GitHub Copilot CLI, and the mechanism is worth understanding before the configuration, because it explains almost every input the action exposes.

The action does not talk to a model endpoint itself. It invokes the Copilot CLI with -p for the prompt, -s and --no-ask-user, and no --allow-tool flags are passed unless you set the copilot-allow-tools input. So the model call is a subprocess invocation of a locally installed CLI, and the action is the wrapper that assembles the command line, runs it and captures the result.

That design has one immediate consequence you have to handle. Because the Copilot CLI is not pre-installed on GitHub-hosted runners, you need to install and authenticate it before invoking this action. The usage example shows the whole sequence.

yaml
name: AI inference
on: workflow_dispatch

jobs:
  inference:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6

      - name: Install Copilot CLI
        run: npm install -g @github/copilot

      - name: Run AI Inference via Copilot
        id: inference
        uses: actions/ai-inference@v1
        with:
          prompt: Summarize the latest changes in this repository.
          model: gpt-4.1
        env:
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT }}

      - name: Print output
        run: echo "${{ steps.inference.outputs.response }}"

The steps are: check out the repository, set up Node, install the CLI globally with npm, then call the action with a prompt and a model, and finally print the response output. Authentication is via the COPILOT_GITHUB_TOKEN environment variable, pointing at a repository secret.

The action itself is Copilot-only. The provider input defaults to copilot and copilot is the only supported value, so the provider input is effectively a placeholder for a future expansion rather than a choice you make today.

The primary language is TypeScript, the licence is MIT, the default branch is main, and the last push was on 2026-10-17. The recent releases are v3 from 2026-07-29, v2.1.1 from 2026-05-25 and v2.1.0 from 2026-05-12, while the package metadata carries a version of 1.0.0, which is the package version rather than the action tag. The readme points at the GitHub Actions toolkit's versioning document for how releases are handled, and the repository includes a script/release helper for tagging and pushing new releases.

Prompt files, and the templating that makes them reusable

Prompt content can be given inline or from files, and there are three file forms.

A text prompt file is the simplest.

yaml
steps:
  - name: Run AI Inference with Text File
    id: inference
    uses: actions/ai-inference@v1
    with:
      prompt-file: ./.github/prompts/prompt.txt

A structured .prompt.yml file adds templating, with two separate inputs for the variables.

yaml
steps:
  - name: Run AI Inference with Prompt YAML
    id: inference
    uses: actions/ai-inference@v1
    with:
      prompt-file: ./.github/prompts/sample.prompt.yml
      input: |
        repo: actions/ai-inference
      file_input: |
        diff: ./artifacts/diff.txt

The two inputs have different jobs. The input input is a YAML map of template variable names to values, and the file_input input is a YAML map of template variable names to file paths, with the file contents injected into the templates. So a variable can be a literal string or the content of a file, and the file route is what lets you feed a diff or a file listing into a prompt without hitting command length limits or shell escaping problems.

The .prompt.yml format itself carries the message list, the model and the templated content.

yaml
messages:
  - role: system
    content: Be concise and concrete.
  - role: user
    content: Summarize changes for {{repo}} using this diff:\n{{diff}}
model: gpt-4.1

The double-brace syntax is the templating, and the values come from the input and file_input maps. The structure with a messages list, each with a role and content, is the same shape the underlying model API expects, which is why the file can hold several turns and not just a single user prompt.

The system prompt has its own inline and file forms, and a file takes precedence when both are set.

yaml
steps:
  - name: Run AI Inference with System Prompt File
    id: inference
    uses: actions/ai-inference@v1
    with:
      prompt: Hello
      system-prompt-file: ./.github/prompts/system.txt

The precedence rules are stated for both prompt and system-prompt pairs: when both are set, the file wins. That is the sensible default, since a file is usually the deliberate, reviewed version and the inline value is the quick one.

The .prompt.yml form is the one to reach for when the prompt is complex, because it keeps a multi-turn message structure and its model choice in a reviewable file in the repository, rather than in a workflow YAML string.

The response file, and why the output alone is not enough

The action exposes two outputs, and the second one exists for a reason.

The response output is the model response text. The response-file output is a path to a temp file containing the response text.

yaml
steps:
  - name: Run AI Inference
    id: inference
    uses: actions/ai-inference@v1
    with:
      prompt: Hello

  - name: Use Response File
    run: |
      echo "Response saved to: ${{ steps.inference.outputs.response-file }}"
      cat "${{ steps.inference.outputs.response-file }}"

The action writes the model response to a temporary file and exposes the path via response-file, so a later step can read it in full.

That is the practical difference. A workflow output is a single string value, which is awkward for long responses to handle downstream. A long response stuffed into the output is hard to work with; the same response in a file can be cat-ed, redirected, parsed, or written into a job summary. For a summarisation or review action, which is the obvious use, the file is the right route and the output is for short responses.

The inputs table is worth reading as a whole, because the precedence rules between pairs are consistent throughout. The prompt and prompt-file pair works the same way as system-prompt and system-prompt-file: the file takes precedence when both are set. The token input defaults to github.token and its description says the value is masked by the action, so the credential does not appear in the log. The model input defaults to gpt-4.1, and its description says to set it empty to let the CLI choose its own default, with claude-sonnet-4.5 given as another example. The copilot-cli-path input points at the binary and, if empty, uses copilot on the PATH, which is what makes the action work with a CLI installed somewhere other than the global npm location. And the copilot-allow-tools input is a comma-separated list of --allow-tool values passed to the CLI, empty by default.

How the action is built and tested

The repository is a TypeScript GitHub Action with the standard JavaScript action layout, and the entries tell you how it is put together.

There is an action.yml at the top level, which the readme says is where the inputs are defined. There is src/ for the source and dist/ for the bundled output, a rollup.config.ts, and a script/ directory holding the release helper. The tests are in __tests__/ with __fixtures__/ alongside them, and the test runner is vitest with --run for the single-shot mode and --watch for the other.

The build script is the interesting one, because it is a two-stage build.

bash
    "package": "npx rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",

Rollup bundles the TypeScript through the TypeScript plugin, and the package script is preceded by the format step so the source is formatted before bundling. The all script chains format, lint, test and package, so the same sequence runs locally and before a release.

The runtime dependencies are small and reveal what the action actually does. @actions/core is the toolkit for inputs, outputs and masking. js-yaml is for parsing the .prompt.yml files and the YAML input maps. tmp is for the temporary response file, with @types/tmp alongside it. That is the whole runtime surface, and the Copilot CLI itself is not a dependency because it is expected to be present on the runner already.

The engines field requires Node 24 or later, and the exports map points the package entry at dist/index.js.

The workflows are the more interesting part of the setup. The README carries four badges: GitHub Super-Linter, CI, a check-dist workflow and CodeQL. The check-dist workflow is the one that matters for a JavaScript action, because a JavaScript action has to commit its bundled dist directory, and that workflow exists to verify the committed bundle matches the source. The CodeQL badge means the action's own code is scanned.

There is also a local-action script, run as npx @github/local-action . src/main.ts .env, which executes the action locally against the .env file so you can iterate without pushing to a branch. The .env.example shows what that file contains, and it is a good illustration of how the action is driven: the ACTIONS_STEP_DEBUG flag for step debug logging, and the inputs in the INPUT_<name> format, which the comment notes is case-sensitive and that hyphens should not be converted to underscores.

What the action will and will not do to the runner

Three notes in the readme constrain the behaviour, and they are the ones to read before you let this run on a repository with real access.

The action invokes Copilot CLI with -p for the prompt, plus -s and --no-ask-user. The no-ask-user flag matters: it means the CLI will not stop and wait for interactive input, which is what makes a non-interactive run possible at all. Without it, a step that expects a response would hang until the job times out.

No --allow-tool flags are passed unless you set copilot-allow-tools. So by default the CLI runs without any tool permissions, which is the right default for a step that is meant to read a diff and say something about it. If you do set the input, it takes a comma-separated list of --allow-tool values, and at that point you are granting the model the ability to run those tools inside the runner, with the runner's permissions.

The third note is that you need to ensure Copilot CLI authentication is configured on the runner, typically via COPILOT_GITHUB_TOKEN. The word typically is doing some work, because the token is what the example passes as a repository secret named COPILOT_PAT, and a repository secret is not available to workflows triggered by a pull request from a fork. That is the practical constraint: the workflow has to be one the fork's code cannot influence, or the step will fail on authentication rather than on the prompt.

The token input itself defaults to github.token, and the action masks it, so the value does not leak into the log. That default is the built-in workflow token, which is scoped to the repository and expires with the job, and it is the reason the action can be used without configuring any credential of your own if the CLI authentication is arranged some other way.

One more constraint is worth stating plainly, and it comes from the action being Copilot-only. If your organisation runs models on its own infrastructure, or uses a different vendor's CLI, this action does not help you. The provider input exists but copilot is the only supported value, so the extension point is a placeholder rather than an option. In that case, running the CLI yourself in a run step gives you the same result with more control, at the cost of handling the response capture and the authentication yourself.

Editorial conclusion

actions/ai-inference fits a team that wants a model call inside an existing workflow, for example a review step on a pull request, without standing up a service or an API key of its own. It does not fit a pipeline that needs a non-Copilot provider, since copilot is the only supported value of the provider input, and it does not fit a self-hosted runner where the Copilot CLI is not already installed and authenticated. Before using it, decide whether the response should come back as an output or as a file, because a file is easier to read, parse or redirect than a single output string.

Frequently asked questions

How do I use the actions/ai-inference action in a workflow?

Install and authenticate the Copilot CLI on the runner first, since it is not pre-installed on GitHub-hosted runners, then add a step using actions/ai-inference@v1 with a prompt and a model, passing COPILOT_GITHUB_TOKEN in the step environment. The result is available as the response output.

What is the difference between prompt and prompt-file in actions/ai-inference?

The prompt input is an inline user prompt and prompt-file points at a .txt or .prompt.yml file. When both are set, prompt-file takes precedence. The .prompt.yml form supports a messages list, templated variables and a model field.

What are the input and file_input options for?

The input option is a YAML map of template variable names to values, and file_input is a YAML map of template variable names to file paths whose contents are injected into the templates. That lets a .prompt.yml file reference something like a diff file without embedding it in the workflow.

Which providers does actions/ai-inference support?

Only Copilot. The provider input defaults to copilot and copilot is the only supported value. The action invokes the Copilot CLI with the prompt, the system prompt and the no-ask-user flag, and no allow-tool flags unless you set copilot-allow-tools.

Official sources

  1. actions/ai-inference on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/actions-ai-inference.svg)](https://hysenlabs.com/projects/actions-ai-inference)

Was this useful?