* 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.
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:Releaseon:push:tags:['v*']permissions:contents:writejobs:release:runs-on:ubuntu-lateststeps:- name:Checkoutuses:actions/checkout@v4with:fetch-depth:0- name:Generate release notesid:notesuses:pullfrog/pullfrog@v0with: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 releaserun:| 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 Checkon:pull_request:types:[closed]jobs:check-release:if:github.event.pull_request.merged == trueruns-on:ubuntu-lateststeps:- uses:actions/checkout@v4- name:Install dependenciesrun:npm install --no-save --no-package-lock zod @actions/core- name:Generate Schemaid:schemarun:| 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 PRid:analysisuses:pullfrog/pullfrog@v0with: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 Resultrun:| # Parse the JSON result using fromJSON()
echo "Version: ${{ fromJSON(steps.analysis.outputs.result).version }}"
echo "Breaking: ${{ fromJSON(steps.analysis.outputs.result).isBreaking }}"