Colin McDonnell 76879b27ec docker testing rewrite: bake the image, drop the allowlist, kill the quoting (#750)
* docker testing rewrite: bake the image, drop the allowlist, kill the quoting

- new `pnpm gha <script>` wrapper. one entry point for running any node
  script in the GHA-like container; replaces the runtime apt-get +
  useradd + chown ceremony in `action/utils/docker.ts`.
- `action/Dockerfile` bakes ubuntu:24.04 + node 24 + gh + jq + sudo +
  testuser at uid 1000. `action/docker-entrypoint.sh` remaps to the host
  uid/gid and `exec`s the requested command — no `bash -c` nesting, no
  `escapeForDoubleQuotes`.
- env passthrough: full `process.env` (+ `.env` via dotenv) flows through
  `--env-file`, multi-line values via `-e` fallback. drops
  `EnvFilterMode` / `testEnvAllowList`.
- image rebuild is content-hash gated on Dockerfile + entrypoint; volume
  is versioned by hash so a stale `node_modules` cache from an old image
  can't poison a new one.
- `action/play.ts` slimmed to a CLI; `run()` extracted to
  `action/utils/runFixture.ts`. drops the `--local` / `PLAY_LOCAL` dual
  mode in favor of explicit `play:local` / `runtest:local` scripts.
- `action/test/run.ts` no longer self-relaunches into docker — that's
  `gha`'s job now.
- `action/test/coverage.ts` `ALWAYS_RUN_ALL` updated to track the new
  files.
- `wiki/docker.md` rewritten (243 → 105 lines). `wiki/action-tests.md`,
  `wiki/billing.md`, `wiki/adversarial.md`, `README.md`, `AGENTS.md` all
  updated to drop `--local` / `PLAY_LOCAL` references.

verified end-to-end: `pnpm play` runs the default fixture against
pullfrog/scratch, exit 0; `sudo unshare --pid` still works inside the
container; `pnpm runtest` boots through the wrapper.

* gha: address review feedback + 3 related issues found locally

review-flagged:
- bare `pnpm gha --build` now builds the image and exits 0 (was
  printing help and exiting 1 — docs claimed it was a valid standalone)
- `initVolumeOwnership` skipped when the named volume already exists;
  saves the ~240ms `docker run … chown` on every warm invocation
- `GIT_SSH_COMMAND` gate widened to any `id_*` private key (was hard-
  coded to `id_rsa`, leaving ed25519-only linux contributors with the
  default ssh config). dropped `-i` so ssh picks whichever key exists
- new `action/.dockerignore` — partial mitigation noted: BuildKit
  (default since docker 23) only sends files referenced by the
  Dockerfile (~42B in practice), so the perf concern is mostly
  hypothetical. file is still worth keeping for `DOCKER_BUILDKIT=0`
  fallback and as documented intent for future `COPY . .` additions

related issues found while validating locally:
- `parseArgs` now stops flag-parsing at the first positional (or
  literal `--`); `pnpm gha test/run.ts --build` previously
  intercepted `--build` as a gha flag instead of forwarding to
  `test/run.ts`
- new `pnpm gha --clean` command prunes orphan `pullfrog-gha:*`
  images and `pullfrog-gha-node-modules-*` volumes whose hash
  doesn't match the current Dockerfile (each Dockerfile/entrypoint
  edit creates a fresh hash and orphans the prior pair, ~600MB +
  ~200MB each — without a cleaner they accumulate silently)
- `--shell` without a TTY now fails fast with an actionable message
  before docker is invoked, instead of producing the confusing
  `the input device is not a TTY` from docker run

wiki updated: documents `--clean`, the parseArgs passthrough rule,
and a new "Reclaiming disk" section.

* gha: fidelity, flexibility, and signal-safety improvements

investigated local fidelity vs the real GHA ubuntu-24.04 runner and
addressed the gaps that have actually bitten contributors or could.

fidelity (image now matches GHA closer):
- bake build-essential, wget, xz-utils, file alongside the existing
  toolset. gh, jq, git, python3, sudo, ssh, build-essential, wget,
  xz, file, unzip, curl all present. native module builds (node-gyp,
  any package missing arm64 prebuilts) now work; common agent shell
  calls don't hit ENOENT
- `host.docker.internal:host-gateway` flag wires the host into the
  container's DNS on linux (macOS Docker Desktop bakes it in). lets
  scripts that hit a local dev server use `API_URL=http://host.docker.
  internal:3100` and work identically on both platforms
- `--init` makes tini PID 1, fixing signal forwarding during the
  pre-exec warmup window (Ctrl-C was previously taking up to 10s to
  tear down because bash-as-PID-1 swallowed the signal)
- pnpm version is correctly pinned via the workspace's
  `packageManager` field — corepack resolves it at install time;
  verified via the new `--doctor` command

flexibility (new affordances):
- `pnpm gha --doctor` runs an inside-the-container fidelity audit:
  os + arch + node/pnpm/python versions, version snapshots of every
  baked tool, env vars (CI, HOME, TMPDIR), uid/gid, and the
  host.docker.internal resolution. useful for "works in CI fails
  locally" or vice versa
- `pnpm gha --build --no-cache` busts the docker layer cache when
  an apt mirror, base image, or external download has changed
  upstream
- entrypoint's `pnpm install` warmup is now wrapped in a `flock` on
  a file in the shared node_modules volume — concurrent `pnpm gha`
  invocations (e.g. play in one terminal, runtest in another)
  serialize their install instead of racing

docs:
- new "Gaps (known)" section in wiki/docker.md explicitly calling
  out the things this system can't do yet, including the missing
  `uses: ./action` semantics gap that
  `.github/workflows/action-gha-e2e-adhoc.yml` currently fills via
  GHA only (designing a local `pnpm gha-action <fixture>` is on the
  roadmap), service containers, parallel-run sharing, and arch
  differences (arm64 vs amd64)

* docs: audit + corrections after testing fronts

self-audit pass for stale references and incomplete pointers:

- wiki/browser.md: `Docker (node:24)` → `pnpm gha container (ubuntu:24.04)`.
  the substance was right (chrome not preinstalled) but the base image
  reference was stale.
- wiki/docker.md: the "Permission errors" troubleshooting line claimed
  the node_modules volume is chowned on every run; now correctly says
  "owned by the host uid on first creation; warm runs skip the chown"
  to match the actual behavior after the initVolumeOwnership fix.
- wiki/action-tests.md: `API_URL` env-var doc now mentions BOTH paths
  (`localhost:` from play:local, `host.docker.internal:` from inside
  the container). Proxy/router recipe now shows both invocations
  side-by-side instead of saying "must use play:local".
- wiki/billing.md: same dual-recipe update for the loop-including-the-
  action proxy walkthrough.
- gha.ts header: expanded the usage block to include --clean / --doctor /
  --no-cache / --shell-TTY, added the host.docker.internal note, and
  pointed at wiki/docker.md for design rationale.

self-document check: a future agent landing on this code can answer
"how do I run a fixture / debug in shell / add a tool / diagnose
fidelity / reach a local dev server" purely from gha.ts header +
wiki/docker.md without spelunking through the entrypoint or git
history.
2026-05-16 03:12:25 +00:00
2026-01-16 08:00:16 +00:00
2026-03-12 05:22:51 +00:00
2025-08-27 16:53:48 -07:00
2026-01-19 08:41:56 +00:00
2026-03-12 05:22:51 +00:00

Green Pullfrog logo
Pullfrog

Bring your favorite coding agent into GitHub


🚀 Pullfrog is in beta! We're onboarding users in waves. Get on the waitlist →


What is Pullfrog?

Pullfrog is a GitHub bot that brings the full power of your favorite coding agents into GitHub. It's open source and powered by GitHub Actions.

  • Tag @pullfrog — Tag @pullfrog in a comment anywhere in your repo. It will pull in any relevant context using the action's internal MCP server and perform the appropriate task.
  • Prompt from the web — Trigger arbitrary tasks from the Pullfrog dashboard
  • Automated triggers — Configure Pullfrog to trigger agent runs in response to specific events. Each of these triggers can be associated with custom prompt instructions.
    • issue created
    • issue labeled
    • PR created
    • PR review created
    • PR review requested
    • and more...

Pullfrog is the bridge between your preferred coding agents and GitHub. Use it for:

  • 🤖 Coding tasks — Tell @pullfrog to implement something and it'll spin up a PR. If CI fails, it'll read the logs and attempt a fix automatically. It'll automatically address any PR reviews too.
  • 🔍 PR review — Coding agents are great at reviewing PRs. Using the "PR created" trigger, you can configure Pullfrog to auto-review new PRs.
  • 🤙 Issue management — Via the "issue created" trigger, Pullfrog can automatically respond to common questions, create implementation plans, and link to related issues/PRs. Or (if you're feeling lucky) you can prompt it to immediately attempt a PR addressing new issues.
  • Literally whatever — Want to have the agent automatically add docs to all new PRs? Cut a new release with agent-written notes on every commit to main? Pullfrog lets you do it.

Standalone Usage

You can also use pullfrog/pullfrog as a step in your own workflows. The action exposes a result output that can be consumed by subsequent steps.

Example: Auto-generate release notes on new tags

name: Release
on:
  push:
    tags: ['v*']

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Generate release notes
        id: notes
        uses: pullfrog/pullfrog@v0
        with:
          prompt: |
            Generate release notes for ${{ github.ref_name }}.
            Compare commits between this tag and the previous tag.
            Format as markdown: summary paragraph, then ### Features, ### Fixes, ### Breaking Changes sections.
            Omit empty sections. Be concise.
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

      # write to file to avoid shell escaping issues with special characters
      - name: Create GitHub release
        run: |
          notesfile="$RUNNER_TEMP/release-notes-$GITHUB_RUN_ID.md"
          printf '%s' "$NOTES" > "$notesfile"
          gh release create ${{ github.ref_name }} --title "${{ github.ref_name }}" --notes-file "$notesfile"
        env:
          GH_TOKEN: ${{ github.token }}
          NOTES: ${{ steps.notes.outputs.result }}

Example: Structured Output with Zod Schema

You can force the agent to return structured JSON output by providing a JSON schema. This allows you to reliably parse and use the agent's response in subsequent workflow steps.

You can define your JSON schema directly or uou can use any validation library that converts to JSON Schema. Here's an example using Zod:

name: Release Check
on:
  pull_request:
    types: [closed]

jobs:
  check-release:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm install --no-save --no-package-lock zod @actions/core

      - name: Generate Schema
        id: schema
        run: |
          node -e '
            import { z } from "zod";
            import { setOutput } from "@actions/core";
            const schema = z.object({
              version: z.string().describe("Semantic version number (e.g. 1.0.0)"),
              isBreaking: z.boolean().describe("Whether this release contains breaking changes"),
              changelog: z.array(z.string()).describe("List of changes in this release"),
            });
            setOutput("schema", JSON.stringify(z.toJSONSchema(schema)));
          '

      - name: Analyze PR
        id: analysis
        uses: pullfrog/pullfrog@v0
        with:
          prompt: |
            Analyze this PR and determine semantic versioning impact.
            Return a JSON object matching the provided schema.
          output_schema: ${{ steps.schema.outputs.schema }}
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

      - name: Process Result
        run: |
          # Parse the JSON result using fromJSON()
          echo "Version: ${{ fromJSON(steps.analysis.outputs.result).version }}"
          echo "Breaking: ${{ fromJSON(steps.analysis.outputs.result).isBreaking }}"
S
Description
Self-hosted Ollama-powered code review bot for Gitea Actions based on pullfrog
Readme MIT 27 MiB
Languages
TypeScript 97.9%
Shell 0.7%
Dockerfile 0.7%
JavaScript 0.7%