shaftoe/pi-coding-agent-action

GitHub action to integrate https://pi.dev/ coding agent with GitHub-compatible CI/CD, issues and PRs

TypeScript

72

639 commits

updated Oct 2, 2026

See the code

README

Pi Coding Agent Action

GitHub release Marketplace TypeScript Build Release Self analyzed PRs Welcome semantic-release Codecov License

A CI/CD action that integrates Pi coding agent with git hosting platform workflows. Works with GitHub, Codeberg, and self-hosted Forgejo instances — any platform that provides GitHub-compatible APIs and CI/CD environment variables.

Inspired by OpenCode's GitHub action.

Features

  • Familiar workflow: Supports the familiar workflow of running Pi coding agent as CLI (loading skills, extensions, AGENTS.md, sharing sessions, etc.) in a GitHub CI/CD workflow.
  • Minimal batteries included: Tries to follow Pi minimalistic phylosophy while providing a comfortable UX out of the box, e.g. pretty print of logs, auto replies to comments, and tools to interact efficiently with git and GitHub-compatible APIs.
  • Composable: Provides useful inputs and outputs for chaining together multiple Pi sessions and/or other GitHub actions/workflows.
  • Integrates with GitHub workflows: Natively integrates with usual GitHub issue/PR workflows, invoke Pi both interactively (e.g. prefixing /pi in an issue/PR comment) and programmatically (e.g. as a step in a workflow).
  • Session sharing: Replicate pi's interactive /share command in CI — export and share agent sessions as a shareable viewer link, using GitHub Gists (default) or a self-hosted Opengist instance.
  • Up-to-date: Pi SDK and dependencies updated regularly.

Use cases

  • Issue assistance: Prefix any new issue description and/or any issue comment with /pi to have the agent analyze the issue, generate a report and/or create a new PR with the fix
  • PR assistance: Prefix any PR comment, review comment or review message with /pi to have the agent review the pull request and/or to apply further changes
  • Automated code reviews: Have Pi review every new pull request automatically
  • Recurring tasks: Schedule Pi to run periodic maintenance tasks such as dependency audits, security scans, documentation updates, or code quality checks
  • Add Pi to your own pipelines: (Optionally) generate prompt from upstream actions/workflows and have Pi do the work in background for you anywhere you like in your workflows

Quick Start

Create a workflow file, e.g., .github/workflows/pi-agent.yml. See the interactive and non-interactive workflows in this repository to get started.

Securing your workflows

[!WARNING] We recommend to be as conservative as possible with how the action can be triggered. Depending on the permissions assigned to your workflow you should consider restricting who's allowed to trigger it e.g. filtering for GitHub user name or role (if github.actor == '<my-user>'). You should also consider disabling automated PRs reviews for forks (if: github.event.pull_request.head.repo.fork == false), see Review PR workflow for an actual example.

[!WARNING] GitHub GITHUB_TOKEN cannot push changes to files under .github/workflows/. This is a GitHub security restriction — even when the workflow has contents: write permission, the automatic GITHUB_TOKEN is never allowed to create or modify workflow files. If you need Pi to create PRs that touch .github/workflows/*.yml, you must provide a Personal Access Token (PAT) with the workflow scope instead of the default GITHUB_TOKEN.

[!CAUTION] Project trust is automatically enabled. The Pi SDK (v0.79.0+) uses a project trust system to decide whether to load project-level resources such as AGENTS.md, .pi settings, project extensions, and skills. In a CI environment there is no interactive user to approve trust, so this action always marks the workspace as trusted (projectTrusted: true) when creating the agent session. This means any AGENTS.md, .pi/ configuration, or project extensions present in the repository checkout will be loaded and followed by the agent. Keep this in mind when deciding what to commit to your repository — anyone with push access can influence agent behavior through these files. See the official Pi documentation for more information on project trust.

Versions and Bundled Dependencies

The develop branch is in constant development while the v2 branch is considered stable, if you don't want the bleeding edge you can pin to a specific release, e.g.

   uses: shaftoe/pi-coding-agent-action@v2.29.1

[!NOTE] This action uses GitHub's immutable releases feature. Once a release tag is published, it cannot be modified — the tag always points to the exact same commit SHA. This means pinning to a release tag (e.g., @v2.20.3) is functionally equivalent to pinning to a commit hash (e.g., @abc123def456) for security and reproducibility purposes. You get the stability of a fixed commit with the readability of semantic versioning. See GitHub's official documentation for more details on immutable releases.

The action is bundled into a single dist/index.js via esbuild so no node_modules are needed at runtime. Non-code Pi SDK assets (HTML templates, theme JSON) are copied to dist/pi-sdk/ and resolved via the PI_PACKAGE_DIR environment variable.

Dependencies (including Pi itself) are kept up to date via the Renovate GitHub app.

If you need to pin to a specific Pi SDK version check out previous release tags and refer to the following table to find the correct version:

DependencyVersionDescription
@actions/core3.0.1GitHub Actions core I/O (inputs, outputs, logging)
@actions/github9.1.1GitHub API client (Octokit wrapper)
@earendil-works/pi-agent-core1.0.0Pi Agent Core — agent orchestration primitives
@earendil-works/pi-ai1.0.0Pi AI — AI model abstractions and providers
@earendil-works/pi-coding-agent1.0.0Pi SDK — AI coding agent runtime
@js-temporal/polyfill0.5.1Temporal API polyfill
@octokit/core7.0.6Octokit REST API client core
@octokit/plugin-rest-endpoint-methods17.0.0Octokit REST API endpoint methods
ignore7.0.6.gitignore-style pattern matching
simple-git3.36.0
typebox1.3.6JSON Schema Type Builder

This table is auto-updated by the develop.yml workflow whenever dependencies change.

[!NOTE] If you don't want to use latest and greatest dependencies, pin the action to a specific release, e.g. uses: shaftoe/pi-coding-agent-action@v2.0.0

Usage

Interactive Workflows

  • Create a GitHub workflow which triggers when comments are added (e.g., issue_comment)
  • Filter by if to only run on the trigger phrase (e.g., startsWith(github.event.comment.body, '/pi '))
  • Add actions/setup-node as prerequisite step (Node version >= v22.x)
  • Finally, add shaftoe/pi-coding-agent-action

Example:

name: Pi Agent

on:
  issue_comment:
    types: [created]

permissions:
  contents: write
  pull-requests: write
  issues: write

jobs:
  pi-agent:
    if: startsWith(github.event.comment.body, '/pi ')
    runs-on: ubuntu-latest
    steps:

      - name: Clone repository to work on
        uses: actions/checkout@v7

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Run Pi agent
        uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: my-provider
          model: some-model
          token: ${{ secrets.MODEL_API_KEY }}
          base_url: https://my-gateway.example.com/v1
          thinking_level: medium

Non-Interactive Workflows

You can use the prompt input to run the agent without requiring a comment trigger. This is useful for automated workflows like PR reviews, scheduled tasks, or prompts generated by a previous step:

- name: Run Pi agent with fixed prompt
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    prompt: 'Review this pull request for security issues' # or e.g. ${{ steps.generate-prompt.outputs.prompt }}

When using the prompt input, the action still enriches the prompt with issue/PR context (title and description) if available in the workflow context.

PR Review with Existing Context

When running automated PR reviews (e.g. on pull_request: [opened, synchronize]), you can instruct the agent to fetch existing review comments before reviewing. This prevents duplicate feedback and provides continuity across re-runs:

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: openai
          model: gpt-5.4
          token: ${{ secrets.OPENAI_API_KEY }}
          prompt: |
            Review this pull request. Before starting, use get_issue_or_pr_thread to
            fetch any existing review comments so you don't duplicate feedback that
            has already been given. Focus on issues, bugs, and security concerns.
            Post your findings as a PR review when done.

[!TIP] The get_issue_or_pr_thread tool returns both regular comments and inline review comments (with file path and line information). Telling the agent to call it first is all you need to provide full review context — no special configuration required.

On-Demand PR Reviews (workflow_dispatch)

Use the pr_number input to run the agent against any pull request on demand, without requiring a triggering comment or event. This is ideal for manual reviews triggered via the GitHub Actions "Run workflow" button or from other automation pipelines.

When pr_number is provided:

  • The action targets the specified PR for all operations (diff, thread, review, etc.).
  • PR context (title, description) is fetched from the GitHub API and included in the prompt.
  • If no prompt input is provided, a default instruction ("Review this pull request and provide feedback") is used.
  • Reactions are automatically skipped since there is no triggering comment.

Example: manual trigger from the GitHub UI

name: On-demand PR Review

on:
  workflow_dispatch:
    inputs:
      pr_number:
        description: 'PR number to review'
        required: true
        type: number
      instruction:
        description: 'Custom instruction (optional)'
        required: false
        default: 'Review this PR for bugs, security issues, and improvements'

jobs:
  review:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: ${{ vars.PROVIDER }}
          model: ${{ vars.MODEL }}
          token: ${{ secrets.API_KEY }}
          pr_number: ${{ inputs.pr_number }}
          prompt: ${{ inputs.instruction }}

Example: triggered by another workflow

You can also call a workflow_dispatch from another workflow step, e.g. to automatically request a review after a specific event:

- name: Trigger PR review
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    gh workflow run on-demand-review.yml \
      --field pr_number=${{ github.event.pull_request.number }} \
      --field instruction="Review the latest changes for regressions"

[!TIP] Combine pr_number with other inputs like thinking_level, loaded_tools, or extensions to customize the review behavior. For example, use loaded_tools to restrict the agent to read-only tools when you only want feedback without automatic fixes.

Comment Update Mode

When running the action in automated workflows (e.g. PR reviews on pull_request: [opened, synchronize]), each push creates a new commit, which can result in many incremental comments from the bot cluttering the PR thread. By default, each run posts a new top-level comment.

Set update_comment: true to instruct the action to update/overwrite its previous comment instead of creating a new one. The action identifies its prior comments by looking for a hidden HTML marker (<!-- pi-coding-agent-comment -->) embedded at the start of the comment body, so it will not touch comments authored by other users or bots.

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: ${{ vars.PROVIDER }}
          model: ${{ vars.MODEL }}
          token: ${{ secrets.API_KEY }}
          prompt: "Review this PR for bugs and security concerns."
          update_comment: true

Behavior notes:

  • Opt-in: defaults to false, so existing workflows are unaffected.
  • Fallback: if update_comment is true but no previous bot comment is found (e.g. first run), a new comment is created as usual.
  • Scope: both top-level issue/PR comments and inline PR review-comment replies are updated. The two namespaces are fetched via different endpoints (issues.listComments vs. pulls.listReviewComments), so the lookup is namespace-aware.
  • Marker: the hidden HTML marker is invisible in rendered Markdown, so it does not change the visual appearance of the comment for end users.

[!NOTE] If you prefer to delete older comments entirely rather than overwrite them, GitHub does not expose a direct "replace" API — the overwrite approach used here preserves comment history (GitHub keeps prior versions accessible via the comment edit history). For fully removing old comments, that would require a separate "prune stale bot comments" tool/step outside the scope of this feature.

Recurring Tasks

You can use the schedule trigger to run the action periodically for automated maintenance tasks like dependency audits, security scans, documentation updates, or code quality checks.

Example: weekly dependency audit

name: Weekly Dependency Audit

on:
  schedule:
    # Runs every Monday at 9:00 AM UTC
    - cron: '0 9 * * 1'
  # Allow manual trigger via the UI
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

jobs:
  dependency-audit:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        id: pi
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: openai
          model: gpt-5.4
          token: ${{ secrets.OPENAI_API_KEY }}
          prompt: |
            Audit the project's dependencies for security vulnerabilities, outdated packages,
            and deprecated APIs. Check package.json, pnpm-lock.yaml, and any lock files.
            If issues are found, create a pull request with updates and a summary of changes.
            If no issues are found, post a comment on the most recent issue or PR indicating
            the audit completed successfully with no findings.

Example: daily documentation sync

name: Daily Documentation Sync

on:
  schedule:
    # Runs daily at 6:00 AM UTC
    - cron: '0 6 * * *'

permissions:
  contents: write
  pull-requests: write

jobs:
  docs-sync:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: openai
          model: gpt-5.4
          token: ${{ secrets.OPENAI_API_KEY }}
          prompt: |
            Check if the documentation is up to date with the latest code changes.
            Review README.md, any docs/ directory, and inline code comments.
            If you find discrepancies, create a pull request with the necessary documentation updates.
          branch_name_template: 'docs/{title}-{number}'

[!TIP] Use workflow_dispatch alongside schedule to allow manual runs for testing or ad-hoc execution. Combine with export_session_html and export_session_jsonl to archive periodic task results as workflow artifacts.

Assignment Triggers

Trigger a Pi session automatically when an issue or pull request is assigned to a specific user (e.g. a bot account). This is useful for triage queues, auto-review pipelines, or routing work to a dedicated agent.

Assignment events carry the full issue/PR payload (title, description, number) but no comment body, so the prompt input is used instead of comment extraction. The action automatically enriches the prompt with the issue/PR context and skips the reaction lifecycle (there is no comment to react to).

Example: trigger when assigned to a bot user

name: Pi on Assign

on:
  issues:
    types: [assigned]
  pull_request:
    types: [assigned]

permissions:
  contents: write
  issues: write
  pull-requests: write

jobs:
  pi-agent:
    # Only run when assigned to the bot user
    if: github.event.assignee.login == 'my-pi-bot'
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0
          # Check out the PR head branch when triggered from a PR
          ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: ${{ vars.PROVIDER }}
          model: ${{ vars.MODEL }}
          token: ${{ secrets.API_KEY }}
          prompt: |
            You have been assigned this issue/PR. Analyze the task and take
            appropriate action — fix bugs, implement features, or review code.
            Use get_issue_or_pr_thread to read the full discussion history
            before starting.

[!TIP] Use separate if conditions or separate jobs to customize behaviour per type — e.g. auto-fix issues but only review PRs:

jobs:
  fix-issue:
    if: github.event_name == 'issues' && github.event.assignee.login == 'my-pi-bot'
    steps:
      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          prompt: 'Implement the described feature and create a PR.'

  review-pr:
    if: github.event_name == 'pull_request' && github.event.assignee.login == 'my-pi-bot'
    steps:
      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          prompt: 'Review this PR for bugs, security, and improvements.'

Custom Extensions

You can load custom Pi extensions to add additional custom tools or modify agent behavior:

- name: Run Pi agent with extensions
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    extensions: |
      npm:pi-subagents
      git:github.com/user/pi-custom-tools
      ./my-local-extension.ts

Supported extension sources:

  • npm packages: npm:package-name or npm:package@version
  • git repositories: git:github.com/user/repo (supports branches with #branch)
  • local files: Relative paths to .ts extension files

Refer to https://pi.dev/docs/latest/extensions to know more about Pi's powerful extensions APIs.

Custom Providers via models.json

The Pi SDK supports registering custom LLM providers (e.g., local servers, API gateways, OpenAI-compatible endpoints) through a models.json configuration file. By default the SDK looks for ~/.pi/agent/models.json. When the file doesn't exist, only the built-in providers are available — identical to the previous behavior.

See the Custom Provider documentation for the full schema reference.

Example: configure a custom provider in a previous workflow step

- name: Configure custom LLM provider
  run: |
    mkdir -p ~/.pi/agent
    cat > ~/.pi/agent/models.json << 'EOF'
    {
      "providers": {
        "my-llm": {
          "baseUrl": "https://api.example.com/v1",
          "apiKey": "${{ secrets.LLM_API_KEY }}",
          "models": [{
            "id": "my-model-v1",
            "name": "My Model V1",
            "api": "openai-chat",
            "cost": { "input": 0.001, "output": 0.002, "cacheRead": 0, "cacheWrite": 0 },
            "contextWindow": 128000,
            "maxTokens": 4096
          }]
        }
      }
    }
    EOF

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    provider: my-llm
    model: my-model-v1
    token: ${{ secrets.LLM_API_KEY }}

[!TIP] You might want to configure custom providers via the extension APIs with a package instead, e.g.:

- name: Run Pi agent
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: my-llm
    model: my-model-v1
    token: ${{ secrets.LLM_API_KEY }}
    extensions: |
      git:github.com/user/my-custom-pi-tools.git

Refer to https://pi.dev/docs/latest/custom-provider for details.

AWS Bedrock

The amazon-bedrock provider runs models hosted on AWS Bedrock (Anthropic Claude, Amazon Nova, Meta Llama, Mistral, and many others) through the Converse Stream API. Model IDs use Bedrock's native format (e.g. anthropic.claude-sonnet-4-5-20250929-v1:0, amazon.nova-pro-v1:0); see the Pi providers list for the full catalogue.

[!IMPORTANT] Bedrock authenticates through the AWS SDK credential chain — it does not use the action's token input. Configure AWS credentials the same way you would for any AWS SDK call: with the official configure-aws-credentials action, an OIDC role, an AWS_PROFILE, or by exporting AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION (a AWS_BEARER_TOKEN_BEDROCK bearer token is also supported). The configured identity must be allowed bedrock:InvokeModelWithResponseStream on the target model.

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/pi-agent
    aws-region: us-east-1

- name: Run Pi agent on Bedrock
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: amazon-bedrock
    model: anthropic.claude-sonnet-4-5-20250929-v1:0
    # No `token` — Bedrock uses the AWS credentials configured above

[!NOTE] The Bedrock implementation (and its @aws-sdk/client-bedrock-runtime dependency) is bundled into dist/index.js and statically registered at startup, so it works out of the box — no runtime node_modules required. This adds ~500 KB to the action bundle. (Scoped to the action only; the pi-cli package is unaffected.)

Disabling Built-in Extensions

By default the action loads all built-in GitHub tools (see Custom Tools for the full list) to help Pi better interact with GitHub Actions environment without relying on external tools like gh or special skills setup. If you want Pi to use only your own custom extensions (or none at all), set load_builtin_extensions to false:

- name: Run Pi agent
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    load_builtin_extensions: false
    extensions: |
      npm:my-custom-github-tools

Selective Tool Loading

Use loaded_tools to control exactly which tools (built-in and Pi's own) are available in the session. This is useful when you want to keep built-in extensions enabled but restrict the agent to a subset of tools.

- name: Run Pi agent (read-only tools only)
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    loaded_tools: |
      get_pr_diff
      create_pull_request_review
      get_issue_or_pr_thread

The default value is all. Tool names must match exactly — the run fails early if a name doesn't correspond to a registered tool.

[!WARNING] loaded_tools can only reference tools that are actually available in the session. If load_builtin_extensions is set to false, built-in GitHub tool names won't be available to list — use load_builtin_extensions: true (the default) and then restrict with loaded_tools instead.

Pi ships two built-in tools that let the model orchestrate other tools rather than calling them one at a time. SDK sessions (which this action uses) do not load them by default, so the action exposes them as opt-in inputs:

  • enable_codemode — adds the codemode tool. The model writes JavaScript that calls tools (including MCP tools) in parallel, filters large results before they reach the model, and can run classifier/image models. This often cuts prompt tokens substantially on tool-heavy tasks, and is particularly useful for large CI logs and PR diffs.
  • enable_tool_search — adds the tool_search tool, which loads tools with deferred exposure into the model's tool set on demand.
- uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    enable_codemode: true
    enable_tool_search: true

[!NOTE] When you also set loaded_tools, the allowlist stays authoritative: include codemode and/or tool_search in the list for them to be active.

MCP Servers

Connect MCP servers to give the agent access to external tools (issue trackers, docs, databases, observability, …). Pass the server definitions as JSON in the mcp_servers input — either a bare map or the mcp.json shape:

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    mcp_servers: |
      {
        "docs": { "url": "https://example.com/mcp" },
        "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] }
      }

Values under headers/env may reference environment variables with ${NAME} (or $NAME), e.g. "Authorization": "Bearer ${DOCS_TOKEN}". References resolve from the action's process environment — that is, the workflow step's env: — so keep tokens there rather than in the input. A reference to an unset variable makes that server fail to connect (it is not passed through literally). !command runs a shell command to produce a value, and $$ escapes a literal $.

Prefer headers/env for credentials: literal values there are registered as log secrets and masked, whereas secrets embedded in args (e.g. ["--api-key=abc123"]) are not.

Configuring any server also loads the codemode, tool_search, and MCP extensions; codemode/tool_search are activated automatically based on each server's exposure (codemode is the default). Set "autoEnableCodemode": false (in the mcp.json shape) to disable that.

Custom Branch Names

You can customize the auto-generated branch names used when Pi creates pull requests. By default, branches follow the pi/issue{number}-{timestamp} pattern. Use the branch_name_template input to override this:

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    branch_name_template: 'feature/{title}-{number}'

Supported Variables

VariableDescriptionExample
{number}Issue or PR number42
{timestamp}Epoch milliseconds1716543210000
{title}Slugified PR titlefix-auth-bug

Examples

TemplateResult
(empty — default)pi/issue42-1716543210000
feature/{title}feature/fix-auth-bug
fix/{number}fix/42
{title}-{number}-{timestamp}fix-auth-bug-42-1716543210000

Pull Requests from a Fork

If there are insufficient rights to push to the repository, agent will open a PR from its own fork. The agent creates the fork automatically on first use and reuses it afterwards.

  1. On first use the agent creates a fork of the repository under the account that owns github_token; subsequent runs reuse it.
  2. The generated branch is pushed to that fork.
  3. The pull request is opened on the repository with the cross-repository head fork-owner:branch. update_pull_request pushes follow-up commits to the fork as well.

This mirrors how human contributors work and keeps agent branches out of the repository's branch list.

[!IMPORTANT] Fork-based PRs need a token that can create forks. When the token cannot push (e.g. a read-only PAT on someone else's repository, or a workflow whose GITHUB_TOKEN is restricted to contents: read), the default secrets.GITHUB_TOKEN authenticates as the repository's github-actions[bot], which cannot own forks — create_pull_request fails with an actionable error in that case. Provide a classic PAT (with the repo scope) or a fine-grained PAT (with repository read/write + fork permissions) via the github_token input.

When the token's owner already owns the repository (e.g. running the agent against your own repository with your PAT), a fork is impossible and unnecessary — the branch is pushed to the repository itself and a same-repository pull request is opened instead.

Injecting Environment Variables

Pi extensions often require environment variables for authentication or configuration. Use the native env: step key to pass variables from your workflow's secrets or configuration into the Pi session:

- name: Run Pi agent with custom env vars
  uses: shaftoe/pi-coding-agent-action@v2
  env:
    MY_API_KEY: ${{ secrets.MY_API_KEY }}
    ANOTHER_SERVICE_TOKEN: ${{ secrets.ANOTHER_SERVICE_TOKEN }}
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

Since the action runs as a single Node.js process, these environment variables are available in process.env and accessible to all Pi extensions.

Token input

The token input is optional and the action auth could also be specified as environment variable instead, e.g:

- name: Run Pi agent with auth env var
  uses: shaftoe/pi-coding-agent-action@v2
  env:
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4

Examples in this documentation tend to favour input because is more consistent with GitHub environment variables/secrets and lets configure the LLM completely via the web admin interface without the need for patching workflow yaml files.

Using Outputs in Downstream Jobs

Use outputs.<job-id>.outputs.<name> to pass action outputs to another step or another job in the same workflow. For example, you can have Pi generate release notes in one job and then create a release in another:

jobs:
  pi-agent:
      # shortened for brevity...
      - name: Run Pi agent
        id: pi          # Required to access outputs from this step
        uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: openai
          model: gpt-5.4
          token: ${{ secrets.OPENAI_API_KEY }}
          prompt: 'Generate release notes for the latest commit'

  publish:
    needs: pi-agent
    if: ${{ needs.pi-agent.outputs.success == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - name: Log results
        run: |
          echo "Response: ${{ needs.pi-agent.outputs.response }}"
          echo "Cost: ${{ needs.pi-agent.outputs.cost }} USD"
          echo "Tokens: ${{ needs.pi-agent.outputs.input_tokens }} in / ${{ needs.pi-agent.outputs.output_tokens }} out"
          echo "Duration: ${{ needs.pi-agent.outputs.duration_seconds }}s"

      - name: Create GitHub release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo '${{ needs.pi-agent.outputs.response }}' > release-notes.md
          gh release create v1.0.0 --notes-file release-notes.md

Session Exports (HTML & JSONL)

The action can export the Pi session in two formats:

  • export_session_html — a self-contained HTML file for human review in any browser
  • export_session_jsonl — a JSONL file (one JSON object per line) for programmatic consumption, data analysis pipelines, or long-term archival

Both are disabled by default. When enabled, their file paths are exposed via the session_html_path and session_jsonl_path outputs. They can be uploaded as workflow artifacts:

- uses: shaftoe/pi-coding-agent-action@v2
  id: pi
  with:
    export_session_html: true
    export_session_jsonl: true
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

- uses: actions/upload-artifact@v7
  if: ${{ steps.pi.outputs.session_html_path || steps.pi.outputs.session_jsonl_path }}
  with:
    name: pi-session-${{ github.event.issue.number || github.event.pull_request.number || github.run_number }}
    path: |
      ${{ steps.pi.outputs.session_html_path }}
      ${{ steps.pi.outputs.session_jsonl_path }}

Session Sharing (/share equivalent)

share_session replicates pi's interactive /share command: it uploads the exported session HTML to a gist and surfaces a shareable viewer link. Two storage backends are supported:

  • GitHub Gists (default) — uploads to a secret gist and links to the pi.dev/session viewer (https://pi.dev/session/#<gistId>). Requires a token with gist scope (see below).
  • Opengist — uploads to a self-hosted instance (e.g. gist.l3x.in). Because the pi.dev viewer can't read non-GitHub gists, the link renders the session directly from the gist's raw HTML. See Opengist backend.

No gh CLI is required — the action calls the gist REST API directly, so it also works from Forgejo/Gitea runners. Both backends honour the PI_SHARE_VIEWER_URL environment variable to point at a self-hosted viewer such as gistviewer.l3x.in — see Custom viewer.

The link is surfaced in two places: the job log footer and the job summary ($GITHUB_STEP_SUMMARY). It is also exposed as the share_url, gist_url, and gist_id outputs for downstream steps.

Enabling share_session auto-enables export_session_html (the gist carries the HTML export's bytes), so you don't need to set both.

[!IMPORTANT] A GitHub token with gist scope is required for the default (github) provider. The default secrets.GITHUB_TOKEN cannot create gists. Set the github_token input to a classic PAT (with the gist scope), a fine-grained PAT (Account → Gists: read/write), or a GitHub App installation token — the same token is used for all GitHub API operations.

[!WARNING] Secret gists are URL-obscured, not access-controlled — anyone with the link can read the rendered session, which may include code, file contents, or secrets the agent touched. Only enable share_session for runs where that exposure is acceptable, and prefer a dedicated bot account so shared gists are easy to audit and delete.

- uses: shaftoe/pi-coding-agent-action@v2
  id: pi
  with:
    share_session: true
    github_token: ${{ secrets.GH_PAT }}   # PAT w/ gist scope — NOT secrets.GITHUB_TOKEN
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

- name: Echo share link
  if: ${{ steps.pi.outputs.share_url }}
  run: echo "Session: ${{ steps.pi.outputs.share_url }}"

[!NOTE] Gist cleanup is your responsibility. The action does not delete shared gists — they accumulate on the account that owns the github_token. This mirrors pi's own /share behaviour and is intentional (the links must stay valid for the viewer to work), but it means gists will pile up over time. To keep things tidy:

  • Use a dedicated bot account for sharing so personal gists don't get cluttered.
  • Each gist's description includes the repo, issue number, and run ID (Pi session — owner/repo#123 (run 456)) for easy identification in the gist list.
  • Periodically prune old gists via the GitHub Gist API or the web UI. The gist_id action output is available for automation — e.g. a scheduled workflow could list and delete gists older than a threshold.
  • The gist_url output points to each gist's page where it can be reviewed or deleted manually.

Opengist backend

To upload shared sessions to a self-hosted Opengist instance (e.g. gist.l3x.in) instead of GitHub Gists, set share_gist_provider: opengist (plus share_gist_api_url and share_gist_token).

Because the pi.dev viewer cannot read non-GitHub gists (it hardcodes api.github.com), the share_url points directly at the gist's raw HTML route. The exported session HTML is self-contained (session data is embedded inline), and Opengist serves .html files with Content-Type: text/html and an inline disposition, so the raw link renders the full session in any browser with no viewer dependency.

[!WARNING] Same-origin XSS surface (Opengist only). With the default (github) provider the rendered session is served from pi.dev — an isolated origin separate from where gists are stored. The Opengist share_url, by contrast, renders the session's HTML on the same origin as your Opengist instance (e.g. gist.l3x.in), which the viewer is typically logged into. The exported HTML carries the viewer JavaScript plus any rendered tool output and file contents; if any of that is not escaped by the session exporter, it executes with the Opengist origin's privileges (session cookies, authenticated /api/... calls) — a stored-XSS vector that does not exist for the GitHub backend. Mitigations: prefer a dedicated or isolated Opengist instance (or a separate viewer account) for shared sessions, and only enable share_session when you trust the session contents.

- uses: shaftoe/pi-coding-agent-action@v2
  id: pi
  with:
    share_session: true
    share_gist_provider: opengist
    share_gist_api_url: https://gist.l3x.in/api/gists   # Opengist REST API lives under /api/, not /api/v1/
    share_gist_token: ${{ secrets.OPENGIST_TOKEN }}       # Opengist access token (og_…) with gist:write scope
    # share_gist_expiration: 7days                        # default; use `never` to keep gists forever
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

- name: Echo share link
  if: ${{ steps.pi.outputs.share_url }}
  run: echo "Session: ${{ steps.pi.outputs.share_url }}"

Notes:

  • The Opengist REST API create endpoint is POST <instance>/api/gists. Create an access token in Settings → Access Tokens (it starts with og_) and grant it the gist:write scope. See the Opengist API docs.
  • Gists are created as unlisted (not listed publicly, but readable via the unguessable URL) — the closest analogue of a GitHub "secret" gist.
  • Shared gists expire after 7 days by default via Opengist's server-side TTL (share_gist_expiration, one of 1hour, 12hours, 1day, 7days, 15days, or never). Shared sessions are ephemeral CI artifacts, so they're cleaned up automatically unless you set share_gist_expiration: never (or use the github provider, which has no expiry support).
  • share_gist_token is required for the opengist provider (an og_… access token with gist:write scope). Unlike the github provider, there is no fallback to github_token — a GitHub token can never authenticate against a self-hosted Opengist instance, so without share_gist_token the share is skipped with a clear notice.
  • The share_url uses the gist's raw route (<gist page>/raw/HEAD/session.html), which Opengist serves as text/html so the self-contained session renders directly in a browser. The HEAD revision resolves to the latest commit (see the Opengist docs). Because this raw-route behaviour is instance-specific, verify it after upgrading Opengist by creating a shared session and opening the link in a fresh browser — if your version doesn't accept HEAD on the raw route, share links will 404.
  • Set PI_SHARE_VIEWER_URL to a custom (non-pi.dev) viewer to instead build a <viewer>#<gistPageUrl> link that a self-hosted viewer can render (see Custom viewer).

Custom viewer

The default (github) provider builds the share_url from the pi.dev viewer (https://pi.dev/session/#<gistId>). Point it at a custom viewer such as gistviewer.l3x.in by setting the PI_SHARE_VIEWER_URL environment variable — no extra input is needed, and it's the same env var pi's interactive /share command reads. The resulting link is <viewer>#<gistId> (the viewer reads the gist ID from the URL fragment, just like pi.dev).

This env var is honoured by both providers, but in different ways:

  • github provider — the link is <viewer>#<gistId> (always, even for the default pi.dev value).
  • opengist provider — by default the share_url is a self-rendering raw-HTML link (the pi.dev viewer can't read non-GitHub gists). But when PI_SHARE_VIEWER_URL points at a non-pi.dev viewer, the link becomes <viewer>#<gistPageUrl> instead, so a self-hosted viewer that fetches a gist by URL can render Opengist sessions just like pi.dev renders GitHub ones. With the pi.dev default (or no env var) the raw self-rendering link is kept.
- uses: shaftoe/pi-coding-agent-action@v2
  id: pi
  env:
    PI_SHARE_VIEWER_URL: https://gistviewer.l3x.in/   # custom viewer instead of pi.dev
  with:
    share_session: true
    share_gist_provider: opengist
    share_gist_api_url: https://gist.l3x.in/api/gists  # Opengist REST API lives under /api/, not /api/v1/
    share_gist_token: ${{ secrets.OPENGIST_TOKEN }}     # Opengist access token (og_…) with gist:write scope
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

- name: Echo share link
  if: ${{ steps.pi.outputs.share_url }}
  run: echo "Session: ${{ steps.pi.outputs.share_url }}"

The resulting share_url is https://gistviewer.l3x.in/#https://gist.l3x.in/bot/<gistId> (the viewer reads the gist page URL from the fragment).

Auto-Compaction

For complex, multi-step tasks that generate a lot of context (e.g. large code reviews, multi-file refactors), the conversation may grow too large for the model's context window. Enable auto_compaction to have Pi automatically summarize older messages when the context fills up:

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    auto_compaction: true

Prompt Cache Warming

Providers expire prompt-cache entries after a period of inactivity, so a pause — for example a long tool execution in the middle of a code review — makes the next provider request pay full input price again. Cache warming re-sends the last request with a one-token output budget shortly before the cache entry expires, keeping the (much cheaper) cached prefix alive.

The SDK's streaming mode (the default) protects prefixes during long tool executions. Set cache_warming: idle to also keep caches warm between prompts — useful when the action processes multiple comment-triggered prompts in one job. Refreshes are billed as a cache read plus one output token and only fire when the expected avoided cache-miss cost exceeds the refresh cost, so they never run at a loss:

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: anthropic
    model: claude-sonnet-4-5
    token: ${{ secrets.ANTHROPIC_API_KEY }}
    cache_warming: idle

Set cache_warming: off to disable warming entirely. Warming requires a known cache lifetime for the model (built in for direct Anthropic; custom models can declare promptCache in models.json).

Inputs

InputDescriptionRequiredDefault
auto_compactionEnable automatic context compaction when the conversation grows too large for the model's context window. Pi summarizes older messages to free up context spaceNofalse
base_urlOptional override for the provider base URL (e.g., to route traffic through a proxy or use an OpenAI-compatible gateway)No-
cache_warmingPrompt cache-warming mode: off, streaming (protect cache prefixes during long tool executions), or idle (also refresh between prompts). Refreshes cost a cache read + one output token and only fire when expected savings exceed the costNostreaming (SDK default)
branch_name_templateTemplate for auto-generated branch names in create_pull_request. Supports variables: {number} (issue/PR number), {timestamp} (epoch ms), {title} (slugified PR title). Default: pi/issue{number}-{timestamp}No-
diff_ignore_patternsSpace-separated list of file patterns to exclude from PR diffs by default (e.g. dist/ package-lock.json). The agent can still provide additional patterns at call timeNo-
diff_max_bytesMaximum diff size in bytes returned by the get_pr_diff toolNo102400
diff_max_linesMaximum number of diff lines returned by the get_pr_diff toolNo1000
enable_codemodeEnable the built-in codemode tool: the model runs JavaScript that orchestrates tools in parallel and can reduce large results before they reach the model. Also auto-activated by codemode-exposure MCP serversNofalse
enable_tool_searchEnable the built-in tool_search tool, which loads deferred-exposure tools (typically MCP servers) on demand. Also auto-activated by deferred-exposure MCP serversNofalse
export_session_htmlExport the session as a self-contained HTML file. Auto-enabled when share_session is trueNofalse
export_session_jsonlExport the session as a JSONL file (one JSON object per line) for programmatic consumptionNofalse
extensionsCustom Pi extensions to load (one per line). Supports npm packages (npm:package-name), git repos (git:github.com/user/repo), or local file pathsNo-
github_tokenGitHub token for API access. The default GITHUB_TOKEN works for all standard operations; to use share_session, provide a PAT/App token with gist scope insteadYes-
load_builtin_extensionsWhether to load built-in GitHub tools (see Custom Tools for the full list)Notrue
loaded_toolsControls which tools are available in the session. Defaults to all. Accepts a list of tool names (one per line) to load — unknown names cause the run to fail earlyNoall
mcp_serversMCP servers to connect, as JSON. Either a bare map ({"name": {"command": ...}} / {"name": {"url": ...}}) or the mcp.json shape ({"mcpServers": {...}, "autoEnableCodemode": true}). headers/env values may use ${NAME} env referencesNo-
modelModel to use (e.g., gpt-5.4, gpt-4o, gemini-2.5-pro)Yes-
platformGit hosting platform the action is running on: github (default), codeberg, forgejo, or gitea (alias for forgejo). Determines platform-specific behaviour such as the action-run URL format in the "View action run" footer. The platform is no longer auto-detected from the server URL — set it explicitly when running on Forgejo/Codeberg/Gitea (e.g. platform: forgejo)Nogithub
pr_numberPull request number to target. Use with workflow_dispatch to run the agent on a specific PR without a triggering event. When set, the action fetches PR context from the API and targets all operations at the specified PRNo-
promptOptional prompt to send to the agent (skips comment extraction)No-
providerLLM provider (openai, google, anthropic, amazon-bedrock, etc.)Yes-
refresh_model_catalogRefresh the provider's model catalog from pi.dev at startup so models newer than the bundled SDK resolve. Set to false to skip the network round-trip and shorten boot time (the bundled model list is used instead)Notrue
share_sessionShare the session like pi's /share command: upload the exported HTML to a gist and surface a viewer link. Uses GitHub Gists by default (share_gist_provider: github) or a self-hosted Opengist instance. Auto-enables export_session_htmlNofalse
share_gist_providerStorage backend for share_session: github (GitHub Gists + pi.dev viewer) or opengist (self-hosted instance; requires share_gist_api_url)Nogithub
share_gist_api_urlAPI URL for the share gist provider. Required for opengist (e.g. https://gist.l3x.in/api/gists); optional override for githubNo-
share_gist_tokenToken used to create the shared gist. Opengist access token (og_…, gist:write scope) for opengist (required — no github_token fallback); optional GitHub PAT for the github provider (falls back to github_token)No-
share_gist_expirationTime-to-live for shared gists (opengist provider only): 1hour, 12hours, 1day, 7days, 15days, or never. Defaults to 7daysNo7days
server_urlOverride the forge server URL (e.g. https://git.example.com) when the runner-advertised GITHUB_SERVER_URL points at an internally-reachable host (e.g. http://localhost:3000 on a Forgejo runner behind Docker). Affects user-facing links (commits, PRs, action runs) only — the API client keeps using the runner's GITHUB_API_URL, and platform selection is controlled by the platform input.No-
thinking_levelModel thinking levelNooff
tokenProvider API token. Required for most providers, but can be omitted when using providers that support alternative auth mechanisms (e.g., google-vertex with Application Default Credentials)No-
triggerTrigger phrase used to invoke the actionNo/pi
update_commentWhether to update/overwrite the bot's previous comment on the issue/PR instead of creating a new one. Useful for reducing noise on incremental commits (e.g. auto-review on pull_request: [opened, synchronize])Nofalse

Refer to Pi documentation for the current list of supported providers / models / etc.

Outputs

The action exposes the following outputs, which can be consumed by downstream steps or jobs:

OutputDescriptionExample
costCost of the invocation in USD (omitted if unavailable)0.042
duration_secondsWall-clock duration of agent execution in seconds12.7
gist_idID of the gist holding the shared session HTML — a GitHub Gist ID or Opengist UUID (when share_session succeeds)abc123def456
gist_urlGist page URL — GitHub Gist or Opengist (when share_session succeeds)https://gist.github.com/bot/abc123def456
input_tokensNumber of input tokens consumed (omitted if unavailable)1500
output_tokensNumber of output tokens generated (omitted if unavailable)800
responseThe main agent response text (or error message on failure)Here is the fix for the bug...
session_html_pathPath to the exported session HTML file (when export_session_html is enabled, or when share_session is enabled)/tmp/pi-session-html/session.html
session_jsonl_pathPath to the exported session JSONL file (when export_session_jsonl is enabled)/tmp/pi-session-jsonl/session.jsonl
share_urlShareable session link. GitHub provider: a viewer link (https://pi.dev/session/#<gistId>, or <PI_SHARE_VIEWER_URL>#<gistId> when the env var is set). Opengist provider: a self-rendering raw-HTML URL on the instance (or <viewer>#<gistPageUrl> when PI_SHARE_VIEWER_URL points at a custom viewer). When share_session succeedshttps://pi.dev/session/#abc123def456
successWhether the agent completed successfully (true / false)true

[!WARNING] Tokens and cost outputs are only set when the underlying provider returns session statistics. They will be absent for providers that don't report token usage.

Custom Tools

The action extends Pi with the following built-in GitHub tools:

ToolDescription
create_pull_request_reviewCreates a pull request review with a summary body, inline comments anchored to specific diff lines, or both. Posts a GitHub Pull Request Review using the pulls.createReview API. Supports summary-only reviews, multi-line comments, diff side selection (LEFT/RIGHT), and review events (COMMENT, APPROVE, REQUEST_CHANGES).
create_pull_requestCreates a new pull request by detecting file changes, creating a branch, committing changes via GitHub API, and opening the PR. Supports dry_run mode for testing without actual PR creation. If there are insufficient rights to push to the repository, it will open a PR from its own fork. The tool creates the fork automatically on first use and reuses it afterwards.
get_ci_statusChecks the CI/CD status for a pull request or commit ref. Returns both check runs and workflow runs with their statuses, conclusions, and URLs. Accepts optional pull_number, ref, status, and conclusion filters. For failed workflow runs, use the returned run_id with get_workflow_run_logs to fetch detailed job logs.
get_issue_or_pr_threadRetrieves the full thread of an issue or pull request including title, body, state, labels, branch info (for PRs), all comments, and review comments (inline comments on specific lines of the diff) for PRs. Useful for understanding the full context before making changes.
get_pr_diffFetches the diff of a pull request on demand. Useful when the agent needs to understand what changed in a PR, e.g. for code reviews or addressing review feedback. Supports configurable max_lines truncation (default: 1000), max byte size cap (default: 100KB), and ignore patterns to filter out noisy paths.
get_workflow_run_logsFetches job logs for a specific GitHub Actions workflow run to diagnose CI failures. Lists all jobs for a run and downloads their logs, truncated to 50KB by default (configurable via max_bytes).
summarize_textSummarizes very long text (CI logs, large threads, file dumps) with a separate one-shot LLM call using the current session model, returning only the summary — keeping the raw text out of the conversation context. Accepts text, an optional focus instruction, and max_words.
update_pull_requestUpdates an existing pull request by pushing new commits to the PR branch (in the fork for fork-based PRs, in the repository otherwise) and optionally updating the title and/or description. Supports dry_run mode for testing without actual modifications.

[!TIP] Set load_builtin_extensions input to false to disable custom tool auto loading.

Goal

If all you want is running Pi inside a CI/CD environment technically you don't need any custom action, something like

- uses: actions/setup-node@v6
- run: npm -g install @earendil-works/pi-coding-agent
- run: pi -p "do something useful for me"

might be just good enough and probably will always be the best fit for a pure "as minimalist as Pi" approach.

On the other hand that's true for almost everything which is offered by the Actions ecosystem, useful and popular Actions are mostly focused on providing a pleasant UX around the raw core functionality they provide.

This project goal is exactly that: to provide a short list of (opt-out) opinionated default features for interacting with and executing Pi agent sessions inside CI/CD environments compatible with GitHub API.

For all the rest you're free and encouraged to just configure the action environment as you would your local Pi instance, e.g. adding files to ~/.pi/agent/, environment variables, etc., and more generally to compose workflow pipelines around this action's inputs and outputs to fullfill your specific needs.

Refer to the official Pi documentation to learn how to tweak Pi to best fit your needs.

Disclaimer

[!NOTE] Codeberg/Forgejo compatibility has been confirmed on self-hosted Forgejo instances, where the action runs the Pi agent as expected (at least in non-interactive mode). When running on Forgejo/Codeberg/Gitea, set the platform input (e.g. platform: forgejo) so platform-specific behaviour — such as the job-level action-run URL in the "View action run" footer — uses the correct format. Not every feature has been exercised yet on these platforms — PRs documenting additional coverage are welcome.

Development

Prerequisites

  • pnpm package manager
  • Node.js 24+

Validation

Before committing, run the following checks:

pnpm run validate

This runs:

  • Code formatting (Prettier)
  • Linting (ESLint)
  • Type checking (TypeScript)
  • Building

Testing

The project uses Vitest for testing:

# Run all tests
pnpm test

# Run tests with coverage
pnpm run test:coverage

# Watch mode for development
pnpm run test:watch

# Run end to end tests (requires LLM to be setup)
pnpm run test:e2e

Project Guidelines

  • Follow the existing code style and conventions
  • Add tests for new functionality
  • Update documentation as needed
  • Use pnpm as the package manager
  • Run pnpm run validate before committing

Releasing

The project uses a develop → v2 branching strategy with fast-forward promotion only (never a hard-reset).

Branches

  • develop — default branch; all PRs target it. Every push runs develop.yml: validate + unit tests + e2e + Fallow. Only when all of those are green does it rebuild dist/ — committing a provenance message of the form chore(dist): rebuild X.Y.Z-develop.<sha> (recording the source SHA and bundled Pi SDK version) — and refresh README deps. Both auto-commits use [skip ci].
  • v2 — release branch. It only ever advances via git merge --ff-only from develop. release.yml runs the e2e gate, then semantic-release (version bump + clean dist/ + tag + GitHub release), and finally fast-forwards the release commit back into develop so the two branches stay equal.

Cutting a release

The recommended path is the Promote develop to v2 workflow (no PAT required, no double-runs):

  1. Actions → "Promote develop to v2" → Run workflow. It fast-forwards v2 to develop@HEAD, pushes v2, then calls release.yml as a reusable workflow to run the e2e gate + semantic-release + merge-back to develop. Needs to be run from the v2 branch.
  2. Optionally run it with dry-run first to confirm the fast-forward is clean (it fails loudly if v2 has diverged from develop).

[!NOTE] semantic-release is driven by conventional commits (see .releaserc.json). feat: → minor, fix:/chore(deps): → patch, BREAKING CHANGE → major. A chore(dist) promote/auto-commit is a non-releasable scope and will not itself trigger a release. If there are no new releasable commits since the last tag, semantic-release is a no-op.

References

License

See LICENSE

ai-assisted
code-review
coding-agents
github-actions
pi-coding-agent

Significant stargazers

plyght

241 followers · starred Jul 2026

Matthias Vallentin

438 followers · starred Apr 2026

shaftoe/pi-coding-agent-action

GitHub action to integrate https://pi.dev/ coding agent with GitHub-compatible CI/CD, issues and PRs

TypeScript

72

639 commits

updated Oct 2, 2026

See the code

README

Pi Coding Agent Action

GitHub release Marketplace TypeScript Build Release Self analyzed PRs Welcome semantic-release Codecov License

A CI/CD action that integrates Pi coding agent with git hosting platform workflows. Works with GitHub, Codeberg, and self-hosted Forgejo instances — any platform that provides GitHub-compatible APIs and CI/CD environment variables.

Inspired by OpenCode's GitHub action.

Features

  • Familiar workflow: Supports the familiar workflow of running Pi coding agent as CLI (loading skills, extensions, AGENTS.md, sharing sessions, etc.) in a GitHub CI/CD workflow.
  • Minimal batteries included: Tries to follow Pi minimalistic phylosophy while providing a comfortable UX out of the box, e.g. pretty print of logs, auto replies to comments, and tools to interact efficiently with git and GitHub-compatible APIs.
  • Composable: Provides useful inputs and outputs for chaining together multiple Pi sessions and/or other GitHub actions/workflows.
  • Integrates with GitHub workflows: Natively integrates with usual GitHub issue/PR workflows, invoke Pi both interactively (e.g. prefixing /pi in an issue/PR comment) and programmatically (e.g. as a step in a workflow).
  • Session sharing: Replicate pi's interactive /share command in CI — export and share agent sessions as a shareable viewer link, using GitHub Gists (default) or a self-hosted Opengist instance.
  • Up-to-date: Pi SDK and dependencies updated regularly.

Use cases

  • Issue assistance: Prefix any new issue description and/or any issue comment with /pi to have the agent analyze the issue, generate a report and/or create a new PR with the fix
  • PR assistance: Prefix any PR comment, review comment or review message with /pi to have the agent review the pull request and/or to apply further changes
  • Automated code reviews: Have Pi review every new pull request automatically
  • Recurring tasks: Schedule Pi to run periodic maintenance tasks such as dependency audits, security scans, documentation updates, or code quality checks
  • Add Pi to your own pipelines: (Optionally) generate prompt from upstream actions/workflows and have Pi do the work in background for you anywhere you like in your workflows

Quick Start

Create a workflow file, e.g., .github/workflows/pi-agent.yml. See the interactive and non-interactive workflows in this repository to get started.

Securing your workflows

[!WARNING] We recommend to be as conservative as possible with how the action can be triggered. Depending on the permissions assigned to your workflow you should consider restricting who's allowed to trigger it e.g. filtering for GitHub user name or role (if github.actor == '<my-user>'). You should also consider disabling automated PRs reviews for forks (if: github.event.pull_request.head.repo.fork == false), see Review PR workflow for an actual example.

[!WARNING] GitHub GITHUB_TOKEN cannot push changes to files under .github/workflows/. This is a GitHub security restriction — even when the workflow has contents: write permission, the automatic GITHUB_TOKEN is never allowed to create or modify workflow files. If you need Pi to create PRs that touch .github/workflows/*.yml, you must provide a Personal Access Token (PAT) with the workflow scope instead of the default GITHUB_TOKEN.

[!CAUTION] Project trust is automatically enabled. The Pi SDK (v0.79.0+) uses a project trust system to decide whether to load project-level resources such as AGENTS.md, .pi settings, project extensions, and skills. In a CI environment there is no interactive user to approve trust, so this action always marks the workspace as trusted (projectTrusted: true) when creating the agent session. This means any AGENTS.md, .pi/ configuration, or project extensions present in the repository checkout will be loaded and followed by the agent. Keep this in mind when deciding what to commit to your repository — anyone with push access can influence agent behavior through these files. See the official Pi documentation for more information on project trust.

Versions and Bundled Dependencies

The develop branch is in constant development while the v2 branch is considered stable, if you don't want the bleeding edge you can pin to a specific release, e.g.

   uses: shaftoe/pi-coding-agent-action@v2.29.1

[!NOTE] This action uses GitHub's immutable releases feature. Once a release tag is published, it cannot be modified — the tag always points to the exact same commit SHA. This means pinning to a release tag (e.g., @v2.20.3) is functionally equivalent to pinning to a commit hash (e.g., @abc123def456) for security and reproducibility purposes. You get the stability of a fixed commit with the readability of semantic versioning. See GitHub's official documentation for more details on immutable releases.

The action is bundled into a single dist/index.js via esbuild so no node_modules are needed at runtime. Non-code Pi SDK assets (HTML templates, theme JSON) are copied to dist/pi-sdk/ and resolved via the PI_PACKAGE_DIR environment variable.

Dependencies (including Pi itself) are kept up to date via the Renovate GitHub app.

If you need to pin to a specific Pi SDK version check out previous release tags and refer to the following table to find the correct version:

DependencyVersionDescription
@actions/core3.0.1GitHub Actions core I/O (inputs, outputs, logging)
@actions/github9.1.1GitHub API client (Octokit wrapper)
@earendil-works/pi-agent-core1.0.0Pi Agent Core — agent orchestration primitives
@earendil-works/pi-ai1.0.0Pi AI — AI model abstractions and providers
@earendil-works/pi-coding-agent1.0.0Pi SDK — AI coding agent runtime
@js-temporal/polyfill0.5.1Temporal API polyfill
@octokit/core7.0.6Octokit REST API client core
@octokit/plugin-rest-endpoint-methods17.0.0Octokit REST API endpoint methods
ignore7.0.6.gitignore-style pattern matching
simple-git3.36.0
typebox1.3.6JSON Schema Type Builder

This table is auto-updated by the develop.yml workflow whenever dependencies change.

[!NOTE] If you don't want to use latest and greatest dependencies, pin the action to a specific release, e.g. uses: shaftoe/pi-coding-agent-action@v2.0.0

Usage

Interactive Workflows

  • Create a GitHub workflow which triggers when comments are added (e.g., issue_comment)
  • Filter by if to only run on the trigger phrase (e.g., startsWith(github.event.comment.body, '/pi '))
  • Add actions/setup-node as prerequisite step (Node version >= v22.x)
  • Finally, add shaftoe/pi-coding-agent-action

Example:

name: Pi Agent

on:
  issue_comment:
    types: [created]

permissions:
  contents: write
  pull-requests: write
  issues: write

jobs:
  pi-agent:
    if: startsWith(github.event.comment.body, '/pi ')
    runs-on: ubuntu-latest
    steps:

      - name: Clone repository to work on
        uses: actions/checkout@v7

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Run Pi agent
        uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: my-provider
          model: some-model
          token: ${{ secrets.MODEL_API_KEY }}
          base_url: https://my-gateway.example.com/v1
          thinking_level: medium

Non-Interactive Workflows

You can use the prompt input to run the agent without requiring a comment trigger. This is useful for automated workflows like PR reviews, scheduled tasks, or prompts generated by a previous step:

- name: Run Pi agent with fixed prompt
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    prompt: 'Review this pull request for security issues' # or e.g. ${{ steps.generate-prompt.outputs.prompt }}

When using the prompt input, the action still enriches the prompt with issue/PR context (title and description) if available in the workflow context.

PR Review with Existing Context

When running automated PR reviews (e.g. on pull_request: [opened, synchronize]), you can instruct the agent to fetch existing review comments before reviewing. This prevents duplicate feedback and provides continuity across re-runs:

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: openai
          model: gpt-5.4
          token: ${{ secrets.OPENAI_API_KEY }}
          prompt: |
            Review this pull request. Before starting, use get_issue_or_pr_thread to
            fetch any existing review comments so you don't duplicate feedback that
            has already been given. Focus on issues, bugs, and security concerns.
            Post your findings as a PR review when done.

[!TIP] The get_issue_or_pr_thread tool returns both regular comments and inline review comments (with file path and line information). Telling the agent to call it first is all you need to provide full review context — no special configuration required.

On-Demand PR Reviews (workflow_dispatch)

Use the pr_number input to run the agent against any pull request on demand, without requiring a triggering comment or event. This is ideal for manual reviews triggered via the GitHub Actions "Run workflow" button or from other automation pipelines.

When pr_number is provided:

  • The action targets the specified PR for all operations (diff, thread, review, etc.).
  • PR context (title, description) is fetched from the GitHub API and included in the prompt.
  • If no prompt input is provided, a default instruction ("Review this pull request and provide feedback") is used.
  • Reactions are automatically skipped since there is no triggering comment.

Example: manual trigger from the GitHub UI

name: On-demand PR Review

on:
  workflow_dispatch:
    inputs:
      pr_number:
        description: 'PR number to review'
        required: true
        type: number
      instruction:
        description: 'Custom instruction (optional)'
        required: false
        default: 'Review this PR for bugs, security issues, and improvements'

jobs:
  review:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: ${{ vars.PROVIDER }}
          model: ${{ vars.MODEL }}
          token: ${{ secrets.API_KEY }}
          pr_number: ${{ inputs.pr_number }}
          prompt: ${{ inputs.instruction }}

Example: triggered by another workflow

You can also call a workflow_dispatch from another workflow step, e.g. to automatically request a review after a specific event:

- name: Trigger PR review
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    gh workflow run on-demand-review.yml \
      --field pr_number=${{ github.event.pull_request.number }} \
      --field instruction="Review the latest changes for regressions"

[!TIP] Combine pr_number with other inputs like thinking_level, loaded_tools, or extensions to customize the review behavior. For example, use loaded_tools to restrict the agent to read-only tools when you only want feedback without automatic fixes.

Comment Update Mode

When running the action in automated workflows (e.g. PR reviews on pull_request: [opened, synchronize]), each push creates a new commit, which can result in many incremental comments from the bot cluttering the PR thread. By default, each run posts a new top-level comment.

Set update_comment: true to instruct the action to update/overwrite its previous comment instead of creating a new one. The action identifies its prior comments by looking for a hidden HTML marker (<!-- pi-coding-agent-comment -->) embedded at the start of the comment body, so it will not touch comments authored by other users or bots.

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: ${{ vars.PROVIDER }}
          model: ${{ vars.MODEL }}
          token: ${{ secrets.API_KEY }}
          prompt: "Review this PR for bugs and security concerns."
          update_comment: true

Behavior notes:

  • Opt-in: defaults to false, so existing workflows are unaffected.
  • Fallback: if update_comment is true but no previous bot comment is found (e.g. first run), a new comment is created as usual.
  • Scope: both top-level issue/PR comments and inline PR review-comment replies are updated. The two namespaces are fetched via different endpoints (issues.listComments vs. pulls.listReviewComments), so the lookup is namespace-aware.
  • Marker: the hidden HTML marker is invisible in rendered Markdown, so it does not change the visual appearance of the comment for end users.

[!NOTE] If you prefer to delete older comments entirely rather than overwrite them, GitHub does not expose a direct "replace" API — the overwrite approach used here preserves comment history (GitHub keeps prior versions accessible via the comment edit history). For fully removing old comments, that would require a separate "prune stale bot comments" tool/step outside the scope of this feature.

Recurring Tasks

You can use the schedule trigger to run the action periodically for automated maintenance tasks like dependency audits, security scans, documentation updates, or code quality checks.

Example: weekly dependency audit

name: Weekly Dependency Audit

on:
  schedule:
    # Runs every Monday at 9:00 AM UTC
    - cron: '0 9 * * 1'
  # Allow manual trigger via the UI
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

jobs:
  dependency-audit:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        id: pi
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: openai
          model: gpt-5.4
          token: ${{ secrets.OPENAI_API_KEY }}
          prompt: |
            Audit the project's dependencies for security vulnerabilities, outdated packages,
            and deprecated APIs. Check package.json, pnpm-lock.yaml, and any lock files.
            If issues are found, create a pull request with updates and a summary of changes.
            If no issues are found, post a comment on the most recent issue or PR indicating
            the audit completed successfully with no findings.

Example: daily documentation sync

name: Daily Documentation Sync

on:
  schedule:
    # Runs daily at 6:00 AM UTC
    - cron: '0 6 * * *'

permissions:
  contents: write
  pull-requests: write

jobs:
  docs-sync:
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: openai
          model: gpt-5.4
          token: ${{ secrets.OPENAI_API_KEY }}
          prompt: |
            Check if the documentation is up to date with the latest code changes.
            Review README.md, any docs/ directory, and inline code comments.
            If you find discrepancies, create a pull request with the necessary documentation updates.
          branch_name_template: 'docs/{title}-{number}'

[!TIP] Use workflow_dispatch alongside schedule to allow manual runs for testing or ad-hoc execution. Combine with export_session_html and export_session_jsonl to archive periodic task results as workflow artifacts.

Assignment Triggers

Trigger a Pi session automatically when an issue or pull request is assigned to a specific user (e.g. a bot account). This is useful for triage queues, auto-review pipelines, or routing work to a dedicated agent.

Assignment events carry the full issue/PR payload (title, description, number) but no comment body, so the prompt input is used instead of comment extraction. The action automatically enriches the prompt with the issue/PR context and skips the reaction lifecycle (there is no comment to react to).

Example: trigger when assigned to a bot user

name: Pi on Assign

on:
  issues:
    types: [assigned]
  pull_request:
    types: [assigned]

permissions:
  contents: write
  issues: write
  pull-requests: write

jobs:
  pi-agent:
    # Only run when assigned to the bot user
    if: github.event.assignee.login == 'my-pi-bot'
    runs-on: ubuntu-latest
    steps:

      - name: Clone the repository to work on
        uses: actions/checkout@v7
        with:
          fetch-depth: 0
          # Check out the PR head branch when triggered from a PR
          ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}

      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: ${{ vars.PROVIDER }}
          model: ${{ vars.MODEL }}
          token: ${{ secrets.API_KEY }}
          prompt: |
            You have been assigned this issue/PR. Analyze the task and take
            appropriate action — fix bugs, implement features, or review code.
            Use get_issue_or_pr_thread to read the full discussion history
            before starting.

[!TIP] Use separate if conditions or separate jobs to customize behaviour per type — e.g. auto-fix issues but only review PRs:

jobs:
  fix-issue:
    if: github.event_name == 'issues' && github.event.assignee.login == 'my-pi-bot'
    steps:
      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          prompt: 'Implement the described feature and create a PR.'

  review-pr:
    if: github.event_name == 'pull_request' && github.event.assignee.login == 'my-pi-bot'
    steps:
      - uses: shaftoe/pi-coding-agent-action@v2
        with:
          prompt: 'Review this PR for bugs, security, and improvements.'

Custom Extensions

You can load custom Pi extensions to add additional custom tools or modify agent behavior:

- name: Run Pi agent with extensions
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    extensions: |
      npm:pi-subagents
      git:github.com/user/pi-custom-tools
      ./my-local-extension.ts

Supported extension sources:

  • npm packages: npm:package-name or npm:package@version
  • git repositories: git:github.com/user/repo (supports branches with #branch)
  • local files: Relative paths to .ts extension files

Refer to https://pi.dev/docs/latest/extensions to know more about Pi's powerful extensions APIs.

Custom Providers via models.json

The Pi SDK supports registering custom LLM providers (e.g., local servers, API gateways, OpenAI-compatible endpoints) through a models.json configuration file. By default the SDK looks for ~/.pi/agent/models.json. When the file doesn't exist, only the built-in providers are available — identical to the previous behavior.

See the Custom Provider documentation for the full schema reference.

Example: configure a custom provider in a previous workflow step

- name: Configure custom LLM provider
  run: |
    mkdir -p ~/.pi/agent
    cat > ~/.pi/agent/models.json << 'EOF'
    {
      "providers": {
        "my-llm": {
          "baseUrl": "https://api.example.com/v1",
          "apiKey": "${{ secrets.LLM_API_KEY }}",
          "models": [{
            "id": "my-model-v1",
            "name": "My Model V1",
            "api": "openai-chat",
            "cost": { "input": 0.001, "output": 0.002, "cacheRead": 0, "cacheWrite": 0 },
            "contextWindow": 128000,
            "maxTokens": 4096
          }]
        }
      }
    }
    EOF

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    provider: my-llm
    model: my-model-v1
    token: ${{ secrets.LLM_API_KEY }}

[!TIP] You might want to configure custom providers via the extension APIs with a package instead, e.g.:

- name: Run Pi agent
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: my-llm
    model: my-model-v1
    token: ${{ secrets.LLM_API_KEY }}
    extensions: |
      git:github.com/user/my-custom-pi-tools.git

Refer to https://pi.dev/docs/latest/custom-provider for details.

AWS Bedrock

The amazon-bedrock provider runs models hosted on AWS Bedrock (Anthropic Claude, Amazon Nova, Meta Llama, Mistral, and many others) through the Converse Stream API. Model IDs use Bedrock's native format (e.g. anthropic.claude-sonnet-4-5-20250929-v1:0, amazon.nova-pro-v1:0); see the Pi providers list for the full catalogue.

[!IMPORTANT] Bedrock authenticates through the AWS SDK credential chain — it does not use the action's token input. Configure AWS credentials the same way you would for any AWS SDK call: with the official configure-aws-credentials action, an OIDC role, an AWS_PROFILE, or by exporting AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION (a AWS_BEARER_TOKEN_BEDROCK bearer token is also supported). The configured identity must be allowed bedrock:InvokeModelWithResponseStream on the target model.

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/pi-agent
    aws-region: us-east-1

- name: Run Pi agent on Bedrock
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: amazon-bedrock
    model: anthropic.claude-sonnet-4-5-20250929-v1:0
    # No `token` — Bedrock uses the AWS credentials configured above

[!NOTE] The Bedrock implementation (and its @aws-sdk/client-bedrock-runtime dependency) is bundled into dist/index.js and statically registered at startup, so it works out of the box — no runtime node_modules required. This adds ~500 KB to the action bundle. (Scoped to the action only; the pi-cli package is unaffected.)

Disabling Built-in Extensions

By default the action loads all built-in GitHub tools (see Custom Tools for the full list) to help Pi better interact with GitHub Actions environment without relying on external tools like gh or special skills setup. If you want Pi to use only your own custom extensions (or none at all), set load_builtin_extensions to false:

- name: Run Pi agent
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    load_builtin_extensions: false
    extensions: |
      npm:my-custom-github-tools

Selective Tool Loading

Use loaded_tools to control exactly which tools (built-in and Pi's own) are available in the session. This is useful when you want to keep built-in extensions enabled but restrict the agent to a subset of tools.

- name: Run Pi agent (read-only tools only)
  uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    loaded_tools: |
      get_pr_diff
      create_pull_request_review
      get_issue_or_pr_thread

The default value is all. Tool names must match exactly — the run fails early if a name doesn't correspond to a registered tool.

[!WARNING] loaded_tools can only reference tools that are actually available in the session. If load_builtin_extensions is set to false, built-in GitHub tool names won't be available to list — use load_builtin_extensions: true (the default) and then restrict with loaded_tools instead.

Pi ships two built-in tools that let the model orchestrate other tools rather than calling them one at a time. SDK sessions (which this action uses) do not load them by default, so the action exposes them as opt-in inputs:

  • enable_codemode — adds the codemode tool. The model writes JavaScript that calls tools (including MCP tools) in parallel, filters large results before they reach the model, and can run classifier/image models. This often cuts prompt tokens substantially on tool-heavy tasks, and is particularly useful for large CI logs and PR diffs.
  • enable_tool_search — adds the tool_search tool, which loads tools with deferred exposure into the model's tool set on demand.
- uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    enable_codemode: true
    enable_tool_search: true

[!NOTE] When you also set loaded_tools, the allowlist stays authoritative: include codemode and/or tool_search in the list for them to be active.

MCP Servers

Connect MCP servers to give the agent access to external tools (issue trackers, docs, databases, observability, …). Pass the server definitions as JSON in the mcp_servers input — either a bare map or the mcp.json shape:

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    mcp_servers: |
      {
        "docs": { "url": "https://example.com/mcp" },
        "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] }
      }

Values under headers/env may reference environment variables with ${NAME} (or $NAME), e.g. "Authorization": "Bearer ${DOCS_TOKEN}". References resolve from the action's process environment — that is, the workflow step's env: — so keep tokens there rather than in the input. A reference to an unset variable makes that server fail to connect (it is not passed through literally). !command runs a shell command to produce a value, and $$ escapes a literal $.

Prefer headers/env for credentials: literal values there are registered as log secrets and masked, whereas secrets embedded in args (e.g. ["--api-key=abc123"]) are not.

Configuring any server also loads the codemode, tool_search, and MCP extensions; codemode/tool_search are activated automatically based on each server's exposure (codemode is the default). Set "autoEnableCodemode": false (in the mcp.json shape) to disable that.

Custom Branch Names

You can customize the auto-generated branch names used when Pi creates pull requests. By default, branches follow the pi/issue{number}-{timestamp} pattern. Use the branch_name_template input to override this:

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    branch_name_template: 'feature/{title}-{number}'

Supported Variables

VariableDescriptionExample
{number}Issue or PR number42
{timestamp}Epoch milliseconds1716543210000
{title}Slugified PR titlefix-auth-bug

Examples

TemplateResult
(empty — default)pi/issue42-1716543210000
feature/{title}feature/fix-auth-bug
fix/{number}fix/42
{title}-{number}-{timestamp}fix-auth-bug-42-1716543210000

Pull Requests from a Fork

If there are insufficient rights to push to the repository, agent will open a PR from its own fork. The agent creates the fork automatically on first use and reuses it afterwards.

  1. On first use the agent creates a fork of the repository under the account that owns github_token; subsequent runs reuse it.
  2. The generated branch is pushed to that fork.
  3. The pull request is opened on the repository with the cross-repository head fork-owner:branch. update_pull_request pushes follow-up commits to the fork as well.

This mirrors how human contributors work and keeps agent branches out of the repository's branch list.

[!IMPORTANT] Fork-based PRs need a token that can create forks. When the token cannot push (e.g. a read-only PAT on someone else's repository, or a workflow whose GITHUB_TOKEN is restricted to contents: read), the default secrets.GITHUB_TOKEN authenticates as the repository's github-actions[bot], which cannot own forks — create_pull_request fails with an actionable error in that case. Provide a classic PAT (with the repo scope) or a fine-grained PAT (with repository read/write + fork permissions) via the github_token input.

When the token's owner already owns the repository (e.g. running the agent against your own repository with your PAT), a fork is impossible and unnecessary — the branch is pushed to the repository itself and a same-repository pull request is opened instead.

Injecting Environment Variables

Pi extensions often require environment variables for authentication or configuration. Use the native env: step key to pass variables from your workflow's secrets or configuration into the Pi session:

- name: Run Pi agent with custom env vars
  uses: shaftoe/pi-coding-agent-action@v2
  env:
    MY_API_KEY: ${{ secrets.MY_API_KEY }}
    ANOTHER_SERVICE_TOKEN: ${{ secrets.ANOTHER_SERVICE_TOKEN }}
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

Since the action runs as a single Node.js process, these environment variables are available in process.env and accessible to all Pi extensions.

Token input

The token input is optional and the action auth could also be specified as environment variable instead, e.g:

- name: Run Pi agent with auth env var
  uses: shaftoe/pi-coding-agent-action@v2
  env:
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4

Examples in this documentation tend to favour input because is more consistent with GitHub environment variables/secrets and lets configure the LLM completely via the web admin interface without the need for patching workflow yaml files.

Using Outputs in Downstream Jobs

Use outputs.<job-id>.outputs.<name> to pass action outputs to another step or another job in the same workflow. For example, you can have Pi generate release notes in one job and then create a release in another:

jobs:
  pi-agent:
      # shortened for brevity...
      - name: Run Pi agent
        id: pi          # Required to access outputs from this step
        uses: shaftoe/pi-coding-agent-action@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          provider: openai
          model: gpt-5.4
          token: ${{ secrets.OPENAI_API_KEY }}
          prompt: 'Generate release notes for the latest commit'

  publish:
    needs: pi-agent
    if: ${{ needs.pi-agent.outputs.success == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - name: Log results
        run: |
          echo "Response: ${{ needs.pi-agent.outputs.response }}"
          echo "Cost: ${{ needs.pi-agent.outputs.cost }} USD"
          echo "Tokens: ${{ needs.pi-agent.outputs.input_tokens }} in / ${{ needs.pi-agent.outputs.output_tokens }} out"
          echo "Duration: ${{ needs.pi-agent.outputs.duration_seconds }}s"

      - name: Create GitHub release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo '${{ needs.pi-agent.outputs.response }}' > release-notes.md
          gh release create v1.0.0 --notes-file release-notes.md

Session Exports (HTML & JSONL)

The action can export the Pi session in two formats:

  • export_session_html — a self-contained HTML file for human review in any browser
  • export_session_jsonl — a JSONL file (one JSON object per line) for programmatic consumption, data analysis pipelines, or long-term archival

Both are disabled by default. When enabled, their file paths are exposed via the session_html_path and session_jsonl_path outputs. They can be uploaded as workflow artifacts:

- uses: shaftoe/pi-coding-agent-action@v2
  id: pi
  with:
    export_session_html: true
    export_session_jsonl: true
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

- uses: actions/upload-artifact@v7
  if: ${{ steps.pi.outputs.session_html_path || steps.pi.outputs.session_jsonl_path }}
  with:
    name: pi-session-${{ github.event.issue.number || github.event.pull_request.number || github.run_number }}
    path: |
      ${{ steps.pi.outputs.session_html_path }}
      ${{ steps.pi.outputs.session_jsonl_path }}

Session Sharing (/share equivalent)

share_session replicates pi's interactive /share command: it uploads the exported session HTML to a gist and surfaces a shareable viewer link. Two storage backends are supported:

  • GitHub Gists (default) — uploads to a secret gist and links to the pi.dev/session viewer (https://pi.dev/session/#<gistId>). Requires a token with gist scope (see below).
  • Opengist — uploads to a self-hosted instance (e.g. gist.l3x.in). Because the pi.dev viewer can't read non-GitHub gists, the link renders the session directly from the gist's raw HTML. See Opengist backend.

No gh CLI is required — the action calls the gist REST API directly, so it also works from Forgejo/Gitea runners. Both backends honour the PI_SHARE_VIEWER_URL environment variable to point at a self-hosted viewer such as gistviewer.l3x.in — see Custom viewer.

The link is surfaced in two places: the job log footer and the job summary ($GITHUB_STEP_SUMMARY). It is also exposed as the share_url, gist_url, and gist_id outputs for downstream steps.

Enabling share_session auto-enables export_session_html (the gist carries the HTML export's bytes), so you don't need to set both.

[!IMPORTANT] A GitHub token with gist scope is required for the default (github) provider. The default secrets.GITHUB_TOKEN cannot create gists. Set the github_token input to a classic PAT (with the gist scope), a fine-grained PAT (Account → Gists: read/write), or a GitHub App installation token — the same token is used for all GitHub API operations.

[!WARNING] Secret gists are URL-obscured, not access-controlled — anyone with the link can read the rendered session, which may include code, file contents, or secrets the agent touched. Only enable share_session for runs where that exposure is acceptable, and prefer a dedicated bot account so shared gists are easy to audit and delete.

- uses: shaftoe/pi-coding-agent-action@v2
  id: pi
  with:
    share_session: true
    github_token: ${{ secrets.GH_PAT }}   # PAT w/ gist scope — NOT secrets.GITHUB_TOKEN
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

- name: Echo share link
  if: ${{ steps.pi.outputs.share_url }}
  run: echo "Session: ${{ steps.pi.outputs.share_url }}"

[!NOTE] Gist cleanup is your responsibility. The action does not delete shared gists — they accumulate on the account that owns the github_token. This mirrors pi's own /share behaviour and is intentional (the links must stay valid for the viewer to work), but it means gists will pile up over time. To keep things tidy:

  • Use a dedicated bot account for sharing so personal gists don't get cluttered.
  • Each gist's description includes the repo, issue number, and run ID (Pi session — owner/repo#123 (run 456)) for easy identification in the gist list.
  • Periodically prune old gists via the GitHub Gist API or the web UI. The gist_id action output is available for automation — e.g. a scheduled workflow could list and delete gists older than a threshold.
  • The gist_url output points to each gist's page where it can be reviewed or deleted manually.

Opengist backend

To upload shared sessions to a self-hosted Opengist instance (e.g. gist.l3x.in) instead of GitHub Gists, set share_gist_provider: opengist (plus share_gist_api_url and share_gist_token).

Because the pi.dev viewer cannot read non-GitHub gists (it hardcodes api.github.com), the share_url points directly at the gist's raw HTML route. The exported session HTML is self-contained (session data is embedded inline), and Opengist serves .html files with Content-Type: text/html and an inline disposition, so the raw link renders the full session in any browser with no viewer dependency.

[!WARNING] Same-origin XSS surface (Opengist only). With the default (github) provider the rendered session is served from pi.dev — an isolated origin separate from where gists are stored. The Opengist share_url, by contrast, renders the session's HTML on the same origin as your Opengist instance (e.g. gist.l3x.in), which the viewer is typically logged into. The exported HTML carries the viewer JavaScript plus any rendered tool output and file contents; if any of that is not escaped by the session exporter, it executes with the Opengist origin's privileges (session cookies, authenticated /api/... calls) — a stored-XSS vector that does not exist for the GitHub backend. Mitigations: prefer a dedicated or isolated Opengist instance (or a separate viewer account) for shared sessions, and only enable share_session when you trust the session contents.

- uses: shaftoe/pi-coding-agent-action@v2
  id: pi
  with:
    share_session: true
    share_gist_provider: opengist
    share_gist_api_url: https://gist.l3x.in/api/gists   # Opengist REST API lives under /api/, not /api/v1/
    share_gist_token: ${{ secrets.OPENGIST_TOKEN }}       # Opengist access token (og_…) with gist:write scope
    # share_gist_expiration: 7days                        # default; use `never` to keep gists forever
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

- name: Echo share link
  if: ${{ steps.pi.outputs.share_url }}
  run: echo "Session: ${{ steps.pi.outputs.share_url }}"

Notes:

  • The Opengist REST API create endpoint is POST <instance>/api/gists. Create an access token in Settings → Access Tokens (it starts with og_) and grant it the gist:write scope. See the Opengist API docs.
  • Gists are created as unlisted (not listed publicly, but readable via the unguessable URL) — the closest analogue of a GitHub "secret" gist.
  • Shared gists expire after 7 days by default via Opengist's server-side TTL (share_gist_expiration, one of 1hour, 12hours, 1day, 7days, 15days, or never). Shared sessions are ephemeral CI artifacts, so they're cleaned up automatically unless you set share_gist_expiration: never (or use the github provider, which has no expiry support).
  • share_gist_token is required for the opengist provider (an og_… access token with gist:write scope). Unlike the github provider, there is no fallback to github_token — a GitHub token can never authenticate against a self-hosted Opengist instance, so without share_gist_token the share is skipped with a clear notice.
  • The share_url uses the gist's raw route (<gist page>/raw/HEAD/session.html), which Opengist serves as text/html so the self-contained session renders directly in a browser. The HEAD revision resolves to the latest commit (see the Opengist docs). Because this raw-route behaviour is instance-specific, verify it after upgrading Opengist by creating a shared session and opening the link in a fresh browser — if your version doesn't accept HEAD on the raw route, share links will 404.
  • Set PI_SHARE_VIEWER_URL to a custom (non-pi.dev) viewer to instead build a <viewer>#<gistPageUrl> link that a self-hosted viewer can render (see Custom viewer).

Custom viewer

The default (github) provider builds the share_url from the pi.dev viewer (https://pi.dev/session/#<gistId>). Point it at a custom viewer such as gistviewer.l3x.in by setting the PI_SHARE_VIEWER_URL environment variable — no extra input is needed, and it's the same env var pi's interactive /share command reads. The resulting link is <viewer>#<gistId> (the viewer reads the gist ID from the URL fragment, just like pi.dev).

This env var is honoured by both providers, but in different ways:

  • github provider — the link is <viewer>#<gistId> (always, even for the default pi.dev value).
  • opengist provider — by default the share_url is a self-rendering raw-HTML link (the pi.dev viewer can't read non-GitHub gists). But when PI_SHARE_VIEWER_URL points at a non-pi.dev viewer, the link becomes <viewer>#<gistPageUrl> instead, so a self-hosted viewer that fetches a gist by URL can render Opengist sessions just like pi.dev renders GitHub ones. With the pi.dev default (or no env var) the raw self-rendering link is kept.
- uses: shaftoe/pi-coding-agent-action@v2
  id: pi
  env:
    PI_SHARE_VIEWER_URL: https://gistviewer.l3x.in/   # custom viewer instead of pi.dev
  with:
    share_session: true
    share_gist_provider: opengist
    share_gist_api_url: https://gist.l3x.in/api/gists  # Opengist REST API lives under /api/, not /api/v1/
    share_gist_token: ${{ secrets.OPENGIST_TOKEN }}     # Opengist access token (og_…) with gist:write scope
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}

- name: Echo share link
  if: ${{ steps.pi.outputs.share_url }}
  run: echo "Session: ${{ steps.pi.outputs.share_url }}"

The resulting share_url is https://gistviewer.l3x.in/#https://gist.l3x.in/bot/<gistId> (the viewer reads the gist page URL from the fragment).

Auto-Compaction

For complex, multi-step tasks that generate a lot of context (e.g. large code reviews, multi-file refactors), the conversation may grow too large for the model's context window. Enable auto_compaction to have Pi automatically summarize older messages when the context fills up:

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: openai
    model: gpt-5.4
    token: ${{ secrets.OPENAI_API_KEY }}
    auto_compaction: true

Prompt Cache Warming

Providers expire prompt-cache entries after a period of inactivity, so a pause — for example a long tool execution in the middle of a code review — makes the next provider request pay full input price again. Cache warming re-sends the last request with a one-token output budget shortly before the cache entry expires, keeping the (much cheaper) cached prefix alive.

The SDK's streaming mode (the default) protects prefixes during long tool executions. Set cache_warming: idle to also keep caches warm between prompts — useful when the action processes multiple comment-triggered prompts in one job. Refreshes are billed as a cache read plus one output token and only fire when the expected avoided cache-miss cost exceeds the refresh cost, so they never run at a loss:

- uses: shaftoe/pi-coding-agent-action@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    provider: anthropic
    model: claude-sonnet-4-5
    token: ${{ secrets.ANTHROPIC_API_KEY }}
    cache_warming: idle

Set cache_warming: off to disable warming entirely. Warming requires a known cache lifetime for the model (built in for direct Anthropic; custom models can declare promptCache in models.json).

Inputs

InputDescriptionRequiredDefault
auto_compactionEnable automatic context compaction when the conversation grows too large for the model's context window. Pi summarizes older messages to free up context spaceNofalse
base_urlOptional override for the provider base URL (e.g., to route traffic through a proxy or use an OpenAI-compatible gateway)No-
cache_warmingPrompt cache-warming mode: off, streaming (protect cache prefixes during long tool executions), or idle (also refresh between prompts). Refreshes cost a cache read + one output token and only fire when expected savings exceed the costNostreaming (SDK default)
branch_name_templateTemplate for auto-generated branch names in create_pull_request. Supports variables: {number} (issue/PR number), {timestamp} (epoch ms), {title} (slugified PR title). Default: pi/issue{number}-{timestamp}No-
diff_ignore_patternsSpace-separated list of file patterns to exclude from PR diffs by default (e.g. dist/ package-lock.json). The agent can still provide additional patterns at call timeNo-
diff_max_bytesMaximum diff size in bytes returned by the get_pr_diff toolNo102400
diff_max_linesMaximum number of diff lines returned by the get_pr_diff toolNo1000
enable_codemodeEnable the built-in codemode tool: the model runs JavaScript that orchestrates tools in parallel and can reduce large results before they reach the model. Also auto-activated by codemode-exposure MCP serversNofalse
enable_tool_searchEnable the built-in tool_search tool, which loads deferred-exposure tools (typically MCP servers) on demand. Also auto-activated by deferred-exposure MCP serversNofalse
export_session_htmlExport the session as a self-contained HTML file. Auto-enabled when share_session is trueNofalse
export_session_jsonlExport the session as a JSONL file (one JSON object per line) for programmatic consumptionNofalse
extensionsCustom Pi extensions to load (one per line). Supports npm packages (npm:package-name), git repos (git:github.com/user/repo), or local file pathsNo-
github_tokenGitHub token for API access. The default GITHUB_TOKEN works for all standard operations; to use share_session, provide a PAT/App token with gist scope insteadYes-
load_builtin_extensionsWhether to load built-in GitHub tools (see Custom Tools for the full list)Notrue
loaded_toolsControls which tools are available in the session. Defaults to all. Accepts a list of tool names (one per line) to load — unknown names cause the run to fail earlyNoall
mcp_serversMCP servers to connect, as JSON. Either a bare map ({"name": {"command": ...}} / {"name": {"url": ...}}) or the mcp.json shape ({"mcpServers": {...}, "autoEnableCodemode": true}). headers/env values may use ${NAME} env referencesNo-
modelModel to use (e.g., gpt-5.4, gpt-4o, gemini-2.5-pro)Yes-
platformGit hosting platform the action is running on: github (default), codeberg, forgejo, or gitea (alias for forgejo). Determines platform-specific behaviour such as the action-run URL format in the "View action run" footer. The platform is no longer auto-detected from the server URL — set it explicitly when running on Forgejo/Codeberg/Gitea (e.g. platform: forgejo)Nogithub
pr_numberPull request number to target. Use with workflow_dispatch to run the agent on a specific PR without a triggering event. When set, the action fetches PR context from the API and targets all operations at the specified PRNo-
promptOptional prompt to send to the agent (skips comment extraction)No-
providerLLM provider (openai, google, anthropic, amazon-bedrock, etc.)Yes-
refresh_model_catalogRefresh the provider's model catalog from pi.dev at startup so models newer than the bundled SDK resolve. Set to false to skip the network round-trip and shorten boot time (the bundled model list is used instead)Notrue
share_sessionShare the session like pi's /share command: upload the exported HTML to a gist and surface a viewer link. Uses GitHub Gists by default (share_gist_provider: github) or a self-hosted Opengist instance. Auto-enables export_session_htmlNofalse
share_gist_providerStorage backend for share_session: github (GitHub Gists + pi.dev viewer) or opengist (self-hosted instance; requires share_gist_api_url)Nogithub
share_gist_api_urlAPI URL for the share gist provider. Required for opengist (e.g. https://gist.l3x.in/api/gists); optional override for githubNo-
share_gist_tokenToken used to create the shared gist. Opengist access token (og_…, gist:write scope) for opengist (required — no github_token fallback); optional GitHub PAT for the github provider (falls back to github_token)No-
share_gist_expirationTime-to-live for shared gists (opengist provider only): 1hour, 12hours, 1day, 7days, 15days, or never. Defaults to 7daysNo7days
server_urlOverride the forge server URL (e.g. https://git.example.com) when the runner-advertised GITHUB_SERVER_URL points at an internally-reachable host (e.g. http://localhost:3000 on a Forgejo runner behind Docker). Affects user-facing links (commits, PRs, action runs) only — the API client keeps using the runner's GITHUB_API_URL, and platform selection is controlled by the platform input.No-
thinking_levelModel thinking levelNooff
tokenProvider API token. Required for most providers, but can be omitted when using providers that support alternative auth mechanisms (e.g., google-vertex with Application Default Credentials)No-
triggerTrigger phrase used to invoke the actionNo/pi
update_commentWhether to update/overwrite the bot's previous comment on the issue/PR instead of creating a new one. Useful for reducing noise on incremental commits (e.g. auto-review on pull_request: [opened, synchronize])Nofalse

Refer to Pi documentation for the current list of supported providers / models / etc.

Outputs

The action exposes the following outputs, which can be consumed by downstream steps or jobs:

OutputDescriptionExample
costCost of the invocation in USD (omitted if unavailable)0.042
duration_secondsWall-clock duration of agent execution in seconds12.7
gist_idID of the gist holding the shared session HTML — a GitHub Gist ID or Opengist UUID (when share_session succeeds)abc123def456
gist_urlGist page URL — GitHub Gist or Opengist (when share_session succeeds)https://gist.github.com/bot/abc123def456
input_tokensNumber of input tokens consumed (omitted if unavailable)1500
output_tokensNumber of output tokens generated (omitted if unavailable)800
responseThe main agent response text (or error message on failure)Here is the fix for the bug...
session_html_pathPath to the exported session HTML file (when export_session_html is enabled, or when share_session is enabled)/tmp/pi-session-html/session.html
session_jsonl_pathPath to the exported session JSONL file (when export_session_jsonl is enabled)/tmp/pi-session-jsonl/session.jsonl
share_urlShareable session link. GitHub provider: a viewer link (https://pi.dev/session/#<gistId>, or <PI_SHARE_VIEWER_URL>#<gistId> when the env var is set). Opengist provider: a self-rendering raw-HTML URL on the instance (or <viewer>#<gistPageUrl> when PI_SHARE_VIEWER_URL points at a custom viewer). When share_session succeedshttps://pi.dev/session/#abc123def456
successWhether the agent completed successfully (true / false)true

[!WARNING] Tokens and cost outputs are only set when the underlying provider returns session statistics. They will be absent for providers that don't report token usage.

Custom Tools

The action extends Pi with the following built-in GitHub tools:

ToolDescription
create_pull_request_reviewCreates a pull request review with a summary body, inline comments anchored to specific diff lines, or both. Posts a GitHub Pull Request Review using the pulls.createReview API. Supports summary-only reviews, multi-line comments, diff side selection (LEFT/RIGHT), and review events (COMMENT, APPROVE, REQUEST_CHANGES).
create_pull_requestCreates a new pull request by detecting file changes, creating a branch, committing changes via GitHub API, and opening the PR. Supports dry_run mode for testing without actual PR creation. If there are insufficient rights to push to the repository, it will open a PR from its own fork. The tool creates the fork automatically on first use and reuses it afterwards.
get_ci_statusChecks the CI/CD status for a pull request or commit ref. Returns both check runs and workflow runs with their statuses, conclusions, and URLs. Accepts optional pull_number, ref, status, and conclusion filters. For failed workflow runs, use the returned run_id with get_workflow_run_logs to fetch detailed job logs.
get_issue_or_pr_threadRetrieves the full thread of an issue or pull request including title, body, state, labels, branch info (for PRs), all comments, and review comments (inline comments on specific lines of the diff) for PRs. Useful for understanding the full context before making changes.
get_pr_diffFetches the diff of a pull request on demand. Useful when the agent needs to understand what changed in a PR, e.g. for code reviews or addressing review feedback. Supports configurable max_lines truncation (default: 1000), max byte size cap (default: 100KB), and ignore patterns to filter out noisy paths.
get_workflow_run_logsFetches job logs for a specific GitHub Actions workflow run to diagnose CI failures. Lists all jobs for a run and downloads their logs, truncated to 50KB by default (configurable via max_bytes).
summarize_textSummarizes very long text (CI logs, large threads, file dumps) with a separate one-shot LLM call using the current session model, returning only the summary — keeping the raw text out of the conversation context. Accepts text, an optional focus instruction, and max_words.
update_pull_requestUpdates an existing pull request by pushing new commits to the PR branch (in the fork for fork-based PRs, in the repository otherwise) and optionally updating the title and/or description. Supports dry_run mode for testing without actual modifications.

[!TIP] Set load_builtin_extensions input to false to disable custom tool auto loading.

Goal

If all you want is running Pi inside a CI/CD environment technically you don't need any custom action, something like

- uses: actions/setup-node@v6
- run: npm -g install @earendil-works/pi-coding-agent
- run: pi -p "do something useful for me"

might be just good enough and probably will always be the best fit for a pure "as minimalist as Pi" approach.

On the other hand that's true for almost everything which is offered by the Actions ecosystem, useful and popular Actions are mostly focused on providing a pleasant UX around the raw core functionality they provide.

This project goal is exactly that: to provide a short list of (opt-out) opinionated default features for interacting with and executing Pi agent sessions inside CI/CD environments compatible with GitHub API.

For all the rest you're free and encouraged to just configure the action environment as you would your local Pi instance, e.g. adding files to ~/.pi/agent/, environment variables, etc., and more generally to compose workflow pipelines around this action's inputs and outputs to fullfill your specific needs.

Refer to the official Pi documentation to learn how to tweak Pi to best fit your needs.

Disclaimer

[!NOTE] Codeberg/Forgejo compatibility has been confirmed on self-hosted Forgejo instances, where the action runs the Pi agent as expected (at least in non-interactive mode). When running on Forgejo/Codeberg/Gitea, set the platform input (e.g. platform: forgejo) so platform-specific behaviour — such as the job-level action-run URL in the "View action run" footer — uses the correct format. Not every feature has been exercised yet on these platforms — PRs documenting additional coverage are welcome.

Development

Prerequisites

  • pnpm package manager
  • Node.js 24+

Validation

Before committing, run the following checks:

pnpm run validate

This runs:

  • Code formatting (Prettier)
  • Linting (ESLint)
  • Type checking (TypeScript)
  • Building

Testing

The project uses Vitest for testing:

# Run all tests
pnpm test

# Run tests with coverage
pnpm run test:coverage

# Watch mode for development
pnpm run test:watch

# Run end to end tests (requires LLM to be setup)
pnpm run test:e2e

Project Guidelines

  • Follow the existing code style and conventions
  • Add tests for new functionality
  • Update documentation as needed
  • Use pnpm as the package manager
  • Run pnpm run validate before committing

Releasing

The project uses a develop → v2 branching strategy with fast-forward promotion only (never a hard-reset).

Branches

  • develop — default branch; all PRs target it. Every push runs develop.yml: validate + unit tests + e2e + Fallow. Only when all of those are green does it rebuild dist/ — committing a provenance message of the form chore(dist): rebuild X.Y.Z-develop.<sha> (recording the source SHA and bundled Pi SDK version) — and refresh README deps. Both auto-commits use [skip ci].
  • v2 — release branch. It only ever advances via git merge --ff-only from develop. release.yml runs the e2e gate, then semantic-release (version bump + clean dist/ + tag + GitHub release), and finally fast-forwards the release commit back into develop so the two branches stay equal.

Cutting a release

The recommended path is the Promote develop to v2 workflow (no PAT required, no double-runs):

  1. Actions → "Promote develop to v2" → Run workflow. It fast-forwards v2 to develop@HEAD, pushes v2, then calls release.yml as a reusable workflow to run the e2e gate + semantic-release + merge-back to develop. Needs to be run from the v2 branch.
  2. Optionally run it with dry-run first to confirm the fast-forward is clean (it fails loudly if v2 has diverged from develop).

[!NOTE] semantic-release is driven by conventional commits (see .releaserc.json). feat: → minor, fix:/chore(deps): → patch, BREAKING CHANGE → major. A chore(dist) promote/auto-commit is a non-releasable scope and will not itself trigger a release. If there are no new releasable commits since the last tag, semantic-release is a no-op.

References

License

See LICENSE

ai-assisted
code-review
coding-agents
github-actions
pi-coding-agent

Significant stargazers

plyght

241 followers · starred Jul 2026

Matthias Vallentin

438 followers · starred Apr 2026

Languages

TypeScript

99.6%