* feat(action): make prepush hook non-blocking after one failure
push_branch now treats the repository's prepush hook as best-effort: it
runs at most once per run, surfaces the failure output if the script
exits non-zero, and every subsequent push_branch call this run skips
the hook so the agent isn't blocked by failures unrelated to its
change. The agent can iterate by running the hook command itself via
the shell tool when shell access is available; push_branch will not
re-run the hook automatically after a failure.
Why: a one-line OSS-allowlist change took 9 minutes (#776) because the
agent retried push_branch six times against a prepush hook that was
failing for env-leak and missing-build-artifact reasons unrelated to
the change. CI catches the same checks on the GitHub side; the local
prepush gate was duplicating work and blocking unrelated fixes.
- ToolState: new prepushFailureCount counter (per-run, never resets)
- executeLifecycleHook: returns structured failure (kind/output/exitCode)
so prepush can compose its own agent-facing message instead of
inheriting the generic retry/no-retry advice meant for setup
- push_branch: composes a shell-mode-aware error message; surfaces
prepushSkipped on the success payload + appends a note to the message
- instructions.ts + wiki/prompt.md + docs/comparisons.mdx: updated to
reflect best-effort semantics
* fix(action): clarify prepush latch semantics + soften static guidance
review fixes from PR #777:
- toolState comment, instructions, success message, tool description:
replace "runs at most once per run" / "first call only" wording with
the actual semantic — successful prepush keeps running on later
push_branch calls; only a hook FAILURE latches the bypass.
- tool description: drop hardcoded "via the shell tool" guidance so
the static description doesn't mislead in shell:disabled runs (the
dynamic agent prompt in instructions.ts already does shell-conditional
messaging).
- LifecycleHookFailure.output JSDoc: match the implementation
(stderr-preferred fallback to stdout, empty for timeout/spawn).
* fix(action): shorten prepush-skip log to terse operator telemetry
the previous log line tried to address the agent ("re-run the hook
command yourself via shell"), but log.info writes to the action
runtime's stdout — the agent never sees it. agent-facing skip
guidance already lives in the error message from
buildPrepushFailureMessage, the success message when bypassed, and
the system prompt in instructions.ts. log line is now just operator
telemetry.
* refactor(action): drop slop from prepush soft-fail
self-audit pass after the previous review-fix round. removed
duplication between code-level comment and the five other places that
already explain the same behavior, tightened verbose JSDoc, and
collapsed redundant clauses in agent-facing strings.
- LifecycleHookFailure → discriminated union. drops the optional
exitCode/spawnError fields (and the empty-output sentinel for
timeout/spawn) plus the corresponding ?? fallbacks in the helper.
- PushBranchTool: 7-line code comment above the latch removed
(toolState field comment + tool description + error message +
success message + system prompt all already cover it). tool
description third sentence dropped (restated the second). success
message tightened to a parenthetical.
- buildPrepushFailureMessage: 4-line JSDoc → 1 line. shared "if you
think the failure could indicate a real bug in your code" prefix
factored out across the shell-conditional branches.
- ToolState.prepushFailureCount comment: 8 lines → 3. the "what" is
in git.ts; comment now only documents the invariant (never
decremented within a run).
- instructions.ts prepush guidance: collapsed nested bullets + ternary
into one paragraph; dropped the "so re-running via shell is the only
way…" tail that restated "push_branch will NOT re-run it".
* fix(action): hint prepush bypass on dirty tree after hook failure
When push_branch blocks on a dirty working tree and the prepush latch
is already set, tell the agent the hook will be skipped once the tree is clean.
* fix(test): narrow CI matrix for lifecycle and toolState changes
Remove lifecycle.ts from ALWAYS_RUN_ALL and add lifecycle.ts + toolState.ts
to push/git agnostic test coverage so PRs touching prepush latch logic run
targeted tests instead of the full matrix.
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 }}"