* replace suffix-based env filtering with default-deny allowlist
filterEnv() now only passes known-safe GitHub Actions runner/system/toolchain
vars plus user-configured allowlist entries to shell subprocesses. GITHUB_TOKEN
and GH_TOKEN are always blocked, even from the user allowlist.
adds envAllowlist field to repo settings with dashboard textarea UI (visible
only when shell isolation is enabled) and wires it through run-context API
to the action runtime.
Made-with: Cursor
* address review: blocked-name warning, JAVA_HOME prefix, stale waitlist copy
- setEnvAllowlist now strips BLOCKED_ENV_NAMES from user input and returns
them so main.ts can log a warning
- move JAVA_HOME to exact names, use JAVA_HOME_ as prefix for clarity
- update stale suffix-based description in waitlist email script
Made-with: Cursor
* fix wiki/security.md snippet: JAVA_HOME -> JAVA_HOME_ to match code
Made-with: Cursor
* UI polish: field-sizing-content on all textareas, rename env allowlist label
- add field-sizing-content to all settings textareas so they auto-expand
to fit content (AgentSettings, ModesSettings, WorkflowsSettings, FlagsSettings)
- rename "Environment variable passthrough" to "Environment allowlist"
with clearer popover copy
- drop "e.g." prefix from env allowlist placeholder
- update docs/security.mdx and wiki/security.md references to match
Made-with: Cursor
* tweak env allowlist popover wording
Made-with: Cursor
* document default allowed variables in security docs with link from popover
Made-with: Cursor
* Update action/utils/secrets.ts
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
---------
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
remove duplicate model and agent log emitters, then print model, agent, push, shell, and timeout on separate startup lines so run settings stay concise and easy to scan.
Made-with: Cursor
review bodies were embedding a task-list snapshot that could capture
stale in-progress state due to timing between the agent's final
TodoWrite and the review submission API call. progress comments
are the authoritative checklist surface — remove the review-body
embedding entirely so there is a single source of truth.
also adds a `completeInProgress` option to `renderCollapsible` so
the progress-comment path can finalize any in-progress items at
render time without mutating tracker state.
Made-with: Cursor
* rename agent key to opencode and add skill invocation coverage.
add skill-invoke tests for claude and opencode with local play-based validation signals, update CI matrices, and include the current tracked refactors in this branch for review.
Made-with: Cursor
* exclude agent-specific skill-invoke tests from wrong agent in CI matrix
* address review follow-up and preserve workflow run UI tweak.
switch changed-agents ci coverage to exercise the opentoad implementation path while keeping the opencode expectation, and include the local workflow run client interaction updates requested on this branch.
Made-with: Cursor
* remove opentoad agent filename from runtime.
rename the opencode harness implementation file from opentoad.ts to opencode.ts and update ci coverage input accordingly so action code no longer carries the old filename.
Made-with: Cursor
* ensure security prompt bypass is set on every test fixture.
this keeps adversarial and permissions harnesses from being blocked by the default prompt-injection refusal path during CI security tests.
Made-with: Cursor
---------
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
* add wiki/betterstack.md documenting log querying, request-ID grouping, and MCP usage
Made-with: Cursor
* fix webhook race conditions: separate runId assignment from data updates
the workflow_run webhook handler had a race where concurrent handlers assigned
the same runId to different pending records. the loser's P2002 silently dropped
data updates (jobId, status, completedAt). fix by splitting into two steps:
assignRunId() handles the race-safe unique assignment, then data updates always
target where: { runId } so they hit the correct record regardless of who won.
also downgrade R2 ObjectLockedByBucketPolicy errors from error to warn level
since duplicate webhook deliveries writing the same key is expected under load.
Made-with: Cursor
* homepage copy refresh + fix skills CLI installation
- update hero to "Agent x GitHub" with new subtagline
- rewrite intro paragraphs: workflow, harness capabilities, billing
- add feature sections: bash isolation, headless browser, MCP tools
- update FAQ answers, footer attribution, free-for-oss copy
- update APP_DESCRIPTION for SEO
- fix skills install: use npx from tmpdir instead of local binary
(the bundled action has no node_modules; running npx from tmpdir
avoids project .npmrc with pnpm settings breaking binary resolution)
- instruct agents to use markdown image syntax in upload_file tool
- start dependency installation eagerly from main.ts
- include event title in task instructions
Made-with: Cursor
* add wiki/betterstack.md documenting log querying, request-ID grouping, and MCP usage
Made-with: Cursor
* fix webhook race conditions: separate runId assignment from data updates
the workflow_run webhook handler had a race where concurrent handlers assigned
the same runId to different pending records. the loser's P2002 silently dropped
data updates (jobId, status, completedAt). fix by splitting into two steps:
assignRunId() handles the race-safe unique assignment, then data updates always
target where: { runId } so they hit the correct record regardless of who won.
also downgrade R2 ObjectLockedByBucketPolicy errors from error to warn level
since duplicate webhook deliveries writing the same key is expected under load.
Made-with: Cursor
* require OIDC verification for DB secrets on run-context endpoint
DB secrets transported via run-context were accessible to any GitHub API
token with read access, bypassing GitHub Actions' fork PR secret isolation.
Now the endpoint requires a valid GitHub Actions OIDC token
(X-GitHub-OIDC-Token header) with a matching repository claim before
returning dbSecrets. Also requires admin for account-scope CLI secret
writes (matching the dashboard), and removes dead redactSecrets code.
Made-with: Cursor
emit real ESM runtime + declaration outputs for programmatic imports, align package exports/types with built files, and add a no-cjs policy note.
Made-with: Cursor
npx was running with cwd set to the action's own directory, which has
package.json with "name": "pullfrog". npm treats the local package as
satisfying the request and skips the registry fetch, then fails to find
the binary (sh: 1: pullfrog: not found). use GITHUB_WORKSPACE instead.
Made-with: Cursor
publish was missing a build step so the npm tarball had no dist/.
switch from NPM_TOKEN to OIDC trusted publishing — explicitly unset
NODE_AUTH_TOKEN so setup-node's .npmrc doesn't override the OIDC flow.
bump version since v0.0.195 tag exists from the failed publish attempt.
Made-with: Cursor
action/.husky prepare script was overriding root husky config, so the
pre-push hook (lint + typecheck + test) never ran. merged the lockfile
sync pre-commit into root .husky/pre-commit and removed action/.husky.
also auto-fixed biome format/import-sort errors from last commit.
Made-with: Cursor
simplify installation-not-found flow by removing ownerHasInstallation
field and collapsing the "selected repos" vs "no access" branches into
a single message with a confirm prompt. improve spinner/log copy
throughout init (secrets, model, workflow, test run).
backfill missing jobId on workflow-run redirect page by querying the
GitHub API for the pullfrog job when jobId is null. add 600ms delay
in handleWorkflowRunInProgress before fetching jobs to avoid racing
the job assignment.
Made-with: Cursor
the postinstall script referenced scripts/generate-proxies.ts which isn't
included in the published npm package, silently breaking every npx install.
replaced the proxy stub approach with turbopack resolveAlias and webpack
conditionNames so both bundlers resolve pullfrog imports to TypeScript
source directly — matching what tsc already does via customConditions.
also moves PR summary format from handleWebhook into modes.ts so the
summarize mode prompt includes it directly.
Made-with: Cursor
PRs created as draft (or by automation tokens that suppress workflow
triggers) never ran preview-create because the workflow only listened
for opened/synchronize. Adding ready_for_review as a trigger ensures
the preview repo gets created when a draft PR is marked ready.
Also makes preview-create.ts idempotent by catching 422 (repo already
exists) so it's safe if both opened and ready_for_review fire.
Made-with: Cursor
the inline `node -e` + `TOKEN=$(...)` approach broke because
`core.getIDToken()` in @actions/core writes `::debug::` and
`::add-mask::` to stdout, polluting the captured value.
`node cli.ts gha token` uses `core.setOutput()` which writes to
the $GITHUB_OUTPUT file instead of stdout.
Made-with: Cursor
* improve incremental review output and fix todo tracker race
- reviewed changes section: summarize at logical-change level with
past-tense verbs, not per-file enumerations
- add TodoTracker.completeAll() to mark all non-cancelled items as
completed before snapshotting the collapsible in review/progress posts
Made-with: Cursor
* completeAll -> completeInProgress: only mark in-progress items as completed
Pending items that were genuinely skipped stay as-is in the collapsible,
so the task list honestly reflects what the agent actually did.
Made-with: Cursor
* fix: make token-exfil test reliable by disabling security instructions and reframing prompt
the test was flaky — agents would randomly refuse (not calling set_output),
refuse politely (calling set_output with refusal text), or cooperate fully,
depending on model mood. two changes:
1. set PULLFROG_DISABLE_SECURITY_INSTRUCTIONS=1 in test env (layer 1)
2. reframe prompt as CI debugging task instead of security test (layer 2)
Made-with: Cursor
* fix: set PULLFROG_DISABLE_SECURITY_INSTRUCTIONS on adversarial test fixtures
without this flag, the system prompt tells agents to refuse anything that
looks malicious — which is exactly what these security pentests ask them to
do. adds the flag to tokenExfil, askpassIntercept, and nobashcreative.
Made-with: Cursor
* set PULLFROG_DISABLE_SECURITY_INSTRUCTIONS on all security-related test fixtures
Made-with: Cursor
the "delegate a subagent" instruction doubled LLM sessions for
every summary run, and "use mini or auto effort" was a no-op
since the agent always runs at high/max effort.
Made-with: Cursor
* fix WorkflowRun mis-assignment when multiple dispatches are in flight
workflow_run_requested fires before GitHub applies the custom run-name,
so display_title has no [suffix]. the old desc ordering picked the newest
pending record, cross-linking enrichment ↔ auto-label records.
switch to FIFO (asc) ordering so records are claimed in dispatch order,
and add a 15s createdAt window to avoid claiming stale records.
fixes#523
Made-with: Cursor
* WIP
* plan: update issue indexing resolution to R2-backed lazy filesystem
replace the direct GitHub tarball + in-memory extraction approach with a
two-phase architecture: streaming tarball sync to R2 (per-file, via
tar-stream) and on-demand lazy loading via just-bash InMemoryFs backed
by R2 GETs. scales to 200K+ file monorepos at <50MB memory overhead.
Made-with: Cursor
* plan: switch to tarball + R2 range requests, add design alternatives rule
update issue indexing plan to use a single uncompressed tar in R2 with
byte-offset index instead of per-file uploads. 2 PUTs per sync vs 10K,
5000x cheaper, trivial lifecycle.
add AGENTS.md rule: generate 3 alternatives before committing to a design.
Made-with: Cursor
* fix typos in AGENTS.md
---------
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
- in Review mode, stop the todo tracker and append the completed task
list as a collapsible section to the review body before submitting
- always delete the progress comment after a review is submitted,
regardless of whether the agent called report_progress
Made-with: Cursor
* refactor: replace narrow parameter types with context objects across action/
pass broader context objects (ToolContext, PromptContext, PostCleanupContext) to
utility functions instead of cherry-picking fields into single-use interfaces.
deletes 8 narrow types, simplifies call sites, and makes buildCommentFooter
synchronous by reading ctx.runId/ctx.jobId directly instead of re-deriving
from env vars and making an extra API call.
Made-with: Cursor
* fix: replace non-null assertion with local guard in validatePushDestination
addresses review feedback — the function now validates pushUrl itself instead
of relying on the caller's check, eliminating the ! assertion.
Made-with: Cursor
* revert: remove GH_TOKEN injection from restricted shell
the original change exposed the git token in restricted-mode shell so
`gh` CLI would work. this is a security regression for public repos: MCP
tools are deliberately constrained (no merge, no release, no arbitrary
API calls), but `gh api` with the token gives full GitHub API access to
any prompt-injected agent.
Made-with: Cursor
* reorder prompt sections: task-first with dynamic TOC
put the actual task at the top of the prompt for primacy, add a
dynamic table of contents, and push system/runtime metadata to the end.
new section order: TOC → YOUR TASK → PROCEDURE → EVENT CONTEXT →
SYSTEM → LEARNINGS → RUNTIME
Made-with: Cursor
* enforce clean working tree: continue session if agent leaves uncommitted changes
after each agent run, check `git status --porcelain`. if dirty, resume
the same session with instructions to commit on a new branch, push, and
open a PR. retries up to 3 times before giving up.
- claude code: capture session_id from result event, use --resume <id>
- opencode: use --continue to resume the last session
- remove --no-session-persistence from claude (needed for --resume)
- update Task mode to clarify branch/push/PR is the default finalize step
Made-with: Cursor
* log full prompt in collapsible group for debugging
Made-with: Cursor
* fix: format tool refs in buildCommitPrompt via formatMcpToolRef
* enforce clean git status: general instructions, stop hook, and Task mode
Made-with: Cursor
* fix: rename stale titleBody references after body leak fix
Made-with: Cursor
---------
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
* WIP
* WIP
* review/incremental-review: always submit review, never call report_progress
The progress comment is auto-deleted by the stranded-comment cleanup in
main.ts when the agent skips report_progress. This makes reviews the
sole PR artifact for both modes, reducing noise.
- soften report_progress tool description to allow mode opt-out
- Review mode: always submit exactly one review (approve or request changes)
- IncrementalReview mode: submit review for substantive outcomes, silently
exit for non-substantive changes (formatting-only pushes produce zero artifacts)
Made-with: Cursor
* incremental-review: clarify approval condition for substantive no-issues case
Made-with: Cursor
* report_progress: s/completed/current task list
Made-with: Cursor
* system instructions: align report_progress guidance with mode opt-out
Made-with: Cursor
* fix autofix body leak, harden prompt against injection, trigger-aware comments
never forward event bodies to the agent prompt — they are user-generated
content and a prompt injection vector. the agent fetches bodies on demand
via MCP tools (checkout_pr, get_issue, etc.).
- always set event.body to null in dispatch(), add promptFromBody: false
to autofix, strip body from nested pull_request object
- replace buildEventTitleBody with buildEventTitle rendering inline
references like PR #497 ("Title") instead of raw markdown headings
- add LEAPING_REASON_MAP for trigger-aware progress comments
(e.g. "CI failure detected. Leaping into action...")
- thread type through buildLeapingIntoActionComment, createLeapingComment,
and updateCommentToLeaping
Made-with: Cursor
* rename translateWorkflowRunType.ts to workflowRunTypes.ts
Made-with: Cursor
models can now be marked `deprecated: true` with a `fallback` slug
pointing to a replacement. `resolveCliModel` follows the chain
recursively (with cycle detection) until it finds a non-deprecated
model. this keeps deprecated models in the registry for backward
compatibility instead of removing them.
marks opencode/mimo-v2-pro-free as deprecated with fallback to
opencode/nemotron-3-super-free.
Made-with: Cursor
* sandbox native filesystem tools to prevent /proc/self/environ exfiltration
the agent's native Read/Grep/Edit tools can bypass the shell sandbox by
reading /proc/self/environ directly. this adds agent-native filesystem
restrictions using the highest-precedence, non-overridable config for each CLI:
OpenCode: OPENCODE_PERMISSION env var with external_directory deny-all + /tmp allow,
plus deletion of untrusted .opencode/plugins/ and .opencode/tools/ before launch.
Claude Code: managed-settings.json at /etc/claude-code/ with denyRead, permissions.deny,
allowManagedPermissionRulesOnly, allowManagedHooksOnly. also --setting-sources user and
--disallowedTools path patterns as belt-and-suspenders.
Made-with: Cursor
* add Glob to Claude Code /proc and /sys deny lists
closes gap identified in review — Glob can enumerate /proc entries.
added to both managed-settings.json permissions.deny and --disallowedTools.
Made-with: Cursor
* run token-exfil test with both agents, hint at native /proc reads
changed tag from "agnostic" (opentoad-only) to "security" so the test
runs with both opentoad and claude. updated prompt to explicitly instruct
the agent to try reading /proc/self/environ via native Read tool.
added API keys to action-agnostic CI job for claude support.
Made-with: Cursor
* move token-exfil to crossagent matrix, remove redundant permissions.deny
- moved token-exfil from agnostic/ to crossagent/ so it runs via the
agent matrix (claude + opentoad in parallel) instead of sequentially
- removed permissions.deny per-tool rules from managed-settings.json;
sandbox.filesystem.denyRead is the single enforcement mechanism
- reverted action-agnostic env vars to minimal set
- updated wiki to match
Made-with: Cursor
* document post-spawn API key deletion analysis in security wiki
evaluated whether API key env vars can be deleted from agent processes
after spawn. OpenCode snapshots env at startup (safe to delete), but
Claude Code re-reads process.env per request (not viable). documented
as further exploration item with per-agent breakdown and caveats.
Made-with: Cursor
* fix stale tokenExfil path references in wiki docs
moved from test/agnostic/ to test/crossagent/ in directory tree
and adversarial test example.
Made-with: Cursor
* revert accidental prisma.config.ts changes
Made-with: Cursor
* hardcode PULLFROG_MODEL per agent in test runner to avoid DB model mismatch
when PULLFROG_AGENT forces a specific agent, the DB-configured model may
belong to a different provider (e.g. openai model with claude agent).
PULLFROG_MODEL short-circuits the DB slug resolution entirely.
Made-with: Cursor
* Update waitlist, run ralph experiments
* fix PR files pagination: use octokit.paginate() for >100 files
* fix garbled FAQ answer on landing page
* track cache read/write tokens in OpenCode agent usage
* wrap dispatch() calls in try/catch to prevent webhook retries on transient failures
* replace raw error messages with generic responses in API routes
* guard request.json() calls with try-catch returning 400 on malformed bodies
* log warning when GraphQL review thread/comment counts hit pagination limits
* reduce review comment cache TTL from 24 hours to 10 minutes
* use select instead of include for proxyKey in workflow run queries
* align Claude agent activity timeout to 5 minutes to match OpenCode agent
* add in-memory dedup for PR close webhooks to prevent duplicate indexing
* extract isPullfrogLogin() helper for shared Pullfrog detection logic
* check response.ok on log fetch in checkSuite.ts
* add 10s timeouts to checkSuite API calls and log fetch
* parallelize proxy key usage API calls with Promise.allSettled
* fix three typos on landing page: colleage, dectects, reponse
* move MAX_STDERR_LINES constant to shared.ts
* add indexes on Repo.accountId and PFUser.accountId FK columns
* remove unused Permission enum from Prisma schema
* populate author and keywords in action/package.json
* use crypto.timingSafeEqual for all secret comparisons
* add missing env vars to globals.ts: R2, webhook, and API secrets
* remove commented-out UserRepo model from Prisma schema
* replace console.log/error with log utility in production API routes
* replace catch(error: any) with proper type guards in getUserRole
* remove stale TODO comment on console page
* handle repository_transferred webhook to update owner
* show toast.error instead of console.error on mode/workflow mutation failures
* add Space key handler for keyboard navigation on workflow run links
* replace role=link spans with button elements for proper accessibility
* add root 404 page with Pullfrog branding
* update ISSUES.md: mark completed items
* mark remaining low-priority UX items as addressed
* add error logging alongside toasts, add check script, update ralph commands
* address review feedback: squash migrations, fix try/catch scope, wire up globals consumers
- squash drop_permission_enum migration into add_indexes migration (one migration per PR)
- move getPullRequest() outside try/catch in mention handler so errors aren't mislogged as "dispatch failed"
- restore key ID in proxyKeys.ts Promise.allSettled error log
- remove accidental asdf.txt and ralph.md files
- wire up globals.ts exports to consumers (r2-uploads, r2-private, verifyHookdeckSignature, sync-usage, forwardPreviewWebhook, dispatch-workflow)
Made-with: Cursor
* update model snapshot (qwen3.6-plus-preview renamed to qwen3.6-plus)
Made-with: Cursor
* Update waitlist, run ralph experiments
* improve review quality: add --effort flag, subagent guidance, remove dead prompts
- add --effort high/max to Claude Code CLI (max for Opus, high for Sonnet/Haiku).
default was silently dropped from high to medium in March 2026.
- add subagent guidance to Review/IncrementalReview modeGuidance for parallel
investigation of large cross-cutting PRs (read-only, no side effects).
- remove "THINK HARDER" from mode prompts (vestigial, no longer controls thinking).
- remove redundant mode.prompt bodies from modes.ts — the actual guidance lives in
modeGuidance (selectMode.ts) and mode.prompt was dead code for all built-in modes
since the delegation system was removed in March.
Made-with: Cursor
* make Mode.prompt optional, remove ModeSchema dead code
prompt is only needed by custom user-defined modes (validated by Zod
modeSchema in utils/schemas/modes.ts). built-in modes get their guidance
from modeGuidance in selectMode.ts. the arktype ModeSchema was never
imported anywhere.
Made-with: Cursor
* make modes.ts the single source of truth for mode guidance
move all mode guidance from modeGuidance in selectMode.ts into
mode.prompt in modes.ts. selectMode.ts now only contains the runtime
tool logic (resolving modes, merging user instructions, handling
PlanEdit/SummaryUpdate overrides). this eliminates the confusing
fallback chain where someone editing mode instructions had to know
to look in selectMode.ts rather than modes.ts.
Made-with: Cursor
* add self-review subagent step to Build mode, update wiki
Build mode now delegates a read-only subagent to review the diff
before committing, catching bugs/logic errors/edge cases that the
builder might miss. Also updates wiki/modes.md to reflect the
single-source-of-truth architecture (modes.ts owns all guidance,
selectMode.ts is pure runtime logic).
Made-with: Cursor
* update model snapshot (openrouter qwen3.6-plus rename)
Made-with: Cursor
model resolution was duplicated inside each agent (opentoad, claude) and
PULLFROG_MODEL override was not considered when choosing the agent. now
resolveModel() runs first in main.ts, its result feeds into resolveAgent()
for agent selection, and the resolved model is passed to the agent via
ctx.resolvedModel. agents only handle their own fallback (opentoad: auto-select
via opencode models, claude: strip provider prefix).
also removes the hardcoded anthropic/claude-sonnet test runner default since
ANTHROPIC_API_KEY is no longer in CI.
Made-with: Cursor
- stop capturing/displaying total_cost_usd from Claude CLI (theoretical cost is misleading for subscription users)
- remove Cost column from action logs table and GitHub Job Summary
- extract SecretsCard into its own sidebar tab with KeyRound icon
- remove children prop from AgentSettingsSection
Made-with: Cursor
* feat: add encrypted account-level secrets with UI for adding API keys
Adds AccountSecret model with AES-256-GCM encryption, API routes for
CRUD, "Add secret" button in model costs section, and injects decrypted
secrets into action env (YAML secrets take precedence).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: repo secrets, sidebar icons, lazy learnings history
- add repo-level secrets with inheritance from org secrets
- add icons to console sidebar sections
- fix learnings history modal: lazy fetch with hover prefetch,
strip content from list response, load content per-expansion
Made-with: Cursor
* add input validation bounds for secrets and fix client-side name filter
Made-with: Cursor
* refactor: migrate all client-side data fetching to TanStack Query
Replace manual useState/useEffect/fetch patterns and the custom
usePolling hook with useQuery, useInfiniteQuery, and useMutation
across the entire frontend for consistent caching, background
refetching, and reactive invalidation.
- ActiveWorkflowRunsSection: useQuery + refetchInterval
- WorkflowRunHistory: useInfiniteQuery + polling query
- LearningsSection: useQuery per revision (lazy)
- FlagsSettings: self-contained useQuery + useMutation
- SecretsCard: useMutation for delete
- AddWorkflowButton, VerifyWorkflowButton: useMutation
- EmailSignupForm, email-waitlist: useMutation
- providers.tsx: enable refetchOnWindowFocus
- Delete usePolling.ts (no remaining consumers)
Made-with: Cursor
* address PR review: squash migrations, rename accountSecrets → dbSecrets
Squash the two separate secrets migrations into a single migration.
Rename the wire format field from accountSecrets to dbSecrets since
it now carries merged account + repo secrets.
Made-with: Cursor
* fix: update proxyKeys.ts imports after cache.ts -> yes package migration
Made-with: Cursor
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Claude Code agent for Anthropic model users
Re-adds Claude Code support (removed in #478) so users with Anthropic API
keys or Claude Code OAuth tokens can use their Claude subscriptions directly.
When an Anthropic model is selected and Claude Code credentials are available,
the system auto-selects the Claude agent instead of OpenCode. The harness
mirrors opentoad's security model: native Bash blocked via --disallowedTools,
MCP ShellTool for restricted shell, ASKPASS for git auth. Includes NDJSON
streaming, provider error detection, cache/cost tracking, browser skill,
and todo progress tracking.
Key changes:
- action/agents/claude.ts: full Claude Code harness
- action/utils/agent.ts: auto-select Claude for anthropic/* models
- action/utils/providerErrors.ts: extracted shared provider error detection
- action/utils/skills.ts: extracted shared skill installation (agent-aware)
- action/models.ts: add CLAUDE_CODE_OAUTH_TOKEN to anthropic envVars
- action/utils/docker.ts: add CLAUDE_CODE_OAUTH_TOKEN to test env allowlist
- CI: add claude to test matrix, pass CLAUDE_CODE_OAUTH_TOKEN secret
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused toolId variable, fix apiKeys test env cleanup
The apiKeys test cleanup stripped *_API_KEY vars but missed
CLAUDE_CODE_OAUTH_TOKEN which doesn't match that pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: strip provider prefix from PULLFROG_MODEL in Claude agent
the env override path was returning the raw value (e.g.
"anthropic/claude-sonnet-4-5") without stripping the provider prefix,
causing the Claude CLI to receive an invalid model ID.
Made-with: Cursor
* fix: remove dead cliPath field, add CLAUDE_CODE_OAUTH_TOKEN to workflows
remove unused cliPath from Claude agent RunParams, and pass
CLAUDE_CODE_OAUTH_TOKEN through all pullfrog.yml workflow templates
so users with Claude Pro/Team subscriptions can use their membership.
Made-with: Cursor
* fix: block Bash subagent in Claude Code disallowedTools
Made-with: Cursor
* chore: update model snapshot (opencode/openrouter latest → qwen3.6-plus-free)
Made-with: Cursor
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix proxy key usage tracking and add OSS spend reporting
replace ProxyKey.disabled with finalizedAt to fix a race where keys
were disabled before their usage was synced, causing all HWM values to
be zero. retireKey now fetches final usage from OpenRouter and records
it atomically with optimistic concurrency. syncAccountUsage skips keys
that fail to fetch rather than recording false zeros.
other fixes:
- wrap OpenRouter API calls in retry logic (exponential backoff)
- reconcileStaleWorkflowRuns now retires proxy keys for completed runs
- subprocess activity timeout only tracks stdout (stderr retry loops
no longer prevent hung agent detection)
- add oss-spend script (single bulk fetch from OpenRouter) and
backfill-proxy-key-usage script
Made-with: Cursor
* address review: move isActiveKey check inside transaction, remove redundant guard
Made-with: Cursor
GitHub markdown needs a blank line between </details> and subsequent
HTML elements. Without it, the footer renders inside the collapsed
section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add prepush lifecycle hook
Add `prepushScript` configuration — an optional shell script that runs
automatically before pushing code to the remote repository. Reuses the
existing `executeLifecycleHook` infrastructure (bash execution, 2-min
timeout, error propagation on non-zero exit). When unconfigured the
hook is a no-op.
Made-with: Cursor
* fix: add prepushScript to run-context API response, fix UI separator
Include prepushScript in the settings returned by the run-context
endpoint so the hook actually fires in production. Also fix the
separator pattern in AgentSettings to match the existing convention
(spacer + hr + spacer instead of margin).
Made-with: Cursor
OpenRouter is a separate model specifier, not an alternative key for
direct providers. Also skip the "pass it through in pullfrog.yml"
instruction when the key is already in the default workflow template.
Made-with: Cursor
* add OpenRouter proxy for managed model routing and OSS program
proxy layer that mints ephemeral OpenRouter keys for users without BYOK
API keys. two paths: pro plan users get their selected model proxied via
OpenRouter; OSS program repos (hard-coded allowlist in config.ts) get
free Claude Opus. BYOK env vars (PULLFROG_MODEL/OPENCODE_MODEL) always
take precedence.
frontend: OSS repos see a static "Opus (Free)" badge with the model
dropdown disabled and no API key requirement. all models now carry
openRouterResolve metadata for proxy target resolution.
Made-with: Cursor
* implement OSS program: proxy infrastructure for free model credits
server-side OSS allowlist determines eligible repos. action mints
ephemeral OpenRouter keys via OIDC-authenticated /api/proxy-token
endpoint (idempotent on runId, $10 per-key safety limit). keys are
disabled on workflow completion when no running refs remain. HWM-based
usage sync tracks cumulative spend per account.
schema: ProxyKey model, Account.usageUsd/activeKeyId, WorkflowRun.proxyKeyId
action: OIDC credential stashing, resolveProxyModel uses server oss flag
frontend: isOss flows from server page to components (no client allowlist)
Made-with: Cursor
* address PR review: repo cross-check, key retirement lifecycle, dead code removal
- proxy-token: verify runId belongs to OIDC-authenticated repo via repo relation
- add retireKey() shared primitive: disable in OpenRouter first, then mark disabled in DB
- rotateKey: retire old active key after swap to prevent orphans
- webhook: replace inline cleanupProxyKey with retireKey calls
- syncAccountUsage: skip disabled keys
- remove vestigial AccountPlan/plan field from action types
- add disabled field to ProxyKey schema + migration
Made-with: Cursor
* replace deprecated opencode/mimo-v2-flash-free with mimo-v2-pro-free
Made-with: Cursor
* fix migration ordering: rename disabled migration to sort after table creation
Made-with: Cursor
* squash proxy key migrations into single migration
Made-with: Cursor
* add preview repo to OSS allowlist for testing
Made-with: Cursor
* populate OSS allowlist from oss-program-invitees.json
Made-with: Cursor
* format oss-program-invitees.json
Made-with: Cursor
* add installed public repos to OSS allowlist
split ossRepos into three provenance-tracked lists:
- internalRepos (pullfrog, colinhacks, RobinTail)
- installedPublicRepos (external public non-fork repos with active installs)
- invitees (from oss-program-invitees.json)
also adds scripts/list-oss-candidates.ts to regenerate the installed list
Made-with: Cursor
* fix: resolve tokens before clearing OIDC env vars
resolveTokens → acquireNewToken → isOIDCAvailable() checks
ACTIONS_ID_TOKEN_REQUEST_URL/TOKEN env vars. The new OIDC
stashing code was deleting them in restricted shell mode before
resolveTokens ran, causing it to fall through to the GitHub App
path which requires GITHUB_APP_ID/GITHUB_PRIVATE_KEY.
Made-with: Cursor
* derive proxy-token auth from OIDC claims, add ensureWorkflowRun upsert
- proxy-token no longer requires body.runId; uses claims.run_id + claims.repository
- shared ensureWorkflowRun upsert called from both webhook and proxy-token
- workflow_run_requested handler now eagerly creates WorkflowRun records
- eliminates race condition between webhook and action proxy-token call
Made-with: Cursor
* hardcode PULLFROG_ACCOUNT_ID, document preview debugging lessons
GitHub node IDs are constant — no reason for this to be an env var.
Removes the trailing-newline bug that caused P2025 errors.
Adds wiki docs on workflow testing, Vercel env gotchas, and Neon
preview branch discovery.
Made-with: Cursor
* fix: parse OpenRouter create-key response correctly
the API returns `key` at the top level, not inside `data`
Made-with: Cursor
* onboarding cards, unlock OSS model selection, simplify console
- add OnboardingCard component with two states: workflow install
and model+test (dispatches "Tell me a joke" for test run)
- replace PromptBox overlay gates with dedicated onboarding cards;
PromptBox is now just the form, always enabled
- use hasWorkflowRuns DB check to decide onboarding vs promptbox
- unlock ModelSelector for OSS repos (was locked to Opus badge);
resolve proxyModel from repo's selected model alias in run-context
- rename "API key" row to "Model costs" with pure client-side states:
OSS covered, auto-resolve, free model, BYOK with env var names
- add "(Recommended)" badge to model aliases with recommended: true
- remove OSS_MODEL_DISPLAY_NAME constant and secrets-fetching logic
Made-with: Cursor
* update stale xai model snapshot
Made-with: Cursor
* rename Permissions to Security, hide git push toggle, add shell isolation toggle with disabled state for public repos, remove opentoad agent name from logs
Made-with: Cursor
* chevron hover states, sidebar hooks/security entries
Made-with: Cursor
* address PR review feedback: rename recommended→preferred, fix dispatch orphan, update wiki docs
- rename `recommended` to `preferred` in model alias registry to distinguish
from the UI "Recommended" badge (which is hardcoded for opus + codex only)
- cancel precreated WorkflowRun when workflow lookup fails in dispatch-workflow
- replace run_sql/vercel env pull in wiki docs with neonctl + Prisma pattern
- extend scripts/neon-branch.ts to output DATABASE_URL via neonctl
- add Recommended badge to GPT Codex alongside Claude Opus in ModelSelector
Made-with: Cursor
* add in-memory trigger dedup with Zod-validated search params
replace scattered manual validation (isValidAction, required review_id/comment_id checks, silent action default) with a discriminated union Zod schema. dedup double-clicks via a module-level Map with 30s TTL — no migration, no new table.
Made-with: Cursor
* update model snapshot (xai latest → grok-4.20-multi-agent-0309)
Made-with: Cursor
when create_pull_request_review is called with no body and no inline
comments, return early with a clear log message instead of hitting
GitHub's 422 or posting a useless "approved" comment.
Made-with: Cursor
* show model name in footer, drop pullfrog.com link
add model slug to buildPullfrogFooter so every Pullfrog comment
displays the active model (e.g. "Using `Big Pickle` (free)" or
"Using `Claude Opus`"). remove the pullfrog.com link from all footers.
Made-with: Cursor
* reject <br/> tags in comment bodies, add prompt guidance
add runtime validation in addFooter that throws if <br/> is followed
by a non-blank line (breaks GitHub heading rendering). the agent sees
the error and retries with clean markdown. also update Summarize mode
prompt to explicitly forbid <br/> tags.
Made-with: Cursor
* fix <br/> guidance: move to event instructions, clarify blank line rule
the formatting rule belongs in DEFAULT_PR_SUMMARY_INSTRUCTIONS (event
instructions), not the Summarize mode prompt. clarify that <br/> must
always be followed by a blank line before headings.
Made-with: Cursor
* generalize block-level HTML rule in summary instructions
add a prominent top-level rule about requiring blank lines between ALL
block-level HTML elements and markdown syntax, not just <br/>.
Made-with: Cursor
* move model to toolState instead of threading through params
model is set once at startup and read everywhere — it belongs on
toolState, not threaded as a separate param through 8 call sites.
postCleanup runs without toolState so it just omits the model label.
Made-with: Cursor
* update models.dev snapshot (openai latest changed)
Made-with: Cursor
* add comment to models snapshot test explaining its purpose
Made-with: Cursor
free models (big-pickle, gpt-5-nano, etc.) define envVars: [] and
isFree: true but validateAgentApiKey always required at least one
provider key. now the validation is model-aware: free models bypass
the check, keyed models validate their specific vars, and auto-select
still requires at least one known key.
closes#483
Made-with: Cursor
* fix workflow detection when repos have many workflows
Switch workflow lookup to GitHub's direct workflow-by-filename API so pullfrog.yml is found even when list endpoints paginate, and paginate installation scans in maintenance scripts to avoid partial coverage.
Made-with: Cursor
* fix review comment line resolution: pre-validate against diff hunks + auto-bisect fallback
when submitting a review with inline comments, the tool now:
1. fetches the PR diff and validates each comment's line range against the actual hunk boundaries
2. moves invalid comments to the review body with a clear explanation
3. on 422 from GitHub (rare API quirks where valid-looking lines are rejected), bisects
comments using disposable pending reviews to isolate failures
4. retries with only the comments GitHub accepts
also fixes getHttpStatus (previously isStatusError) which wasn't recognizing Octokit errors,
and warns in the tool description that each call creates a permanent visible review.
Made-with: Cursor
* remove bisect fallback, anchor review to checkout sha, make start_line optional
- drop the auto-bisect-on-422 logic entirely; pre-validation catches the
real issues and the 422 catch now just throws a clear actionable error
- anchor review submission to checkoutSha so line numbers match the diff
the agent actually analyzed (avoids stale-line 422s from new pushes)
- make start_line optional and only set start_line/start_side when it
differs from line (single-line comments don't need the range fields)
- improve headMovedDuringReview detection to use latestHeadSha directly
Made-with: Cursor
* drop review comment pre-validation in favor of pinned commit_id
The pre-validation (listFiles + hunk parsing) was checking comments
against the current PR diff, but the review is now anchored to
checkoutSha. When HEAD moves, pre-validation checks the wrong diff
and can false-reject valid comments. GitHub's own commit_id-anchored
validation is the correct source of truth.
Made-with: Cursor
* add logging to fetchExistingSummaryComment for duplicate summary debug
Made-with: Cursor
* fix duplicate summary comments: guard create_issue_comment for existing summaries
When select_mode finds an existing summary comment (existingSummaryCommentId),
create_issue_comment with type: "Summary" now auto-redirects to update instead
of creating a new comment. Belt-and-suspenders for the token fix in selectMode.ts.
Made-with: Cursor
* document api auth patterns to prevent token misuse
add wiki/api-auth.md explaining the two auth patterns (GitHub token vs
Pullfrog JWT) and when to use each. add auth comments to all action-facing
routes and their callers so the correct token is obvious.
Made-with: Cursor
* fix models.dev snapshot: filter beta models, add tiebreaker
Skip models with any status (beta, deprecated) so nightly/experimental
releases don't cause snapshot churn. Add lexicographic tiebreaker for
stable ordering when release dates match.
Made-with: Cursor
* add tests to pre-push hook
Made-with: Cursor
* fix: decouple summary dispatch from re-review gate on pull_request_synchronize
The summary workflow was never dispatched on new commits because the
pull_request_synchronize handler broke early when prReReview was disabled,
before reaching the prSummaryComment check. Now re-review and summary
are dispatched independently.
Made-with: Cursor
* resolve merge conflicts in rebase.md and checkout.ts
Made-with: Cursor
* fix: restore checkout.ts and rebase.md from remote
Made-with: Cursor
- split triggers.mdx into direct-prompting, pr-reviews, issue-enrichment, coding-tasks
- rename manual-setup.mdx to headless-action.mdx (CI integration)
- reorganize sidebar into Getting started / Usage / Reference groups
- add redirects for /triggers and /manual-setup
- add PULLFROG_MODEL env var support across action, workflows, and docs
- rewrite models.mdx with aliases, free models, resolution chain, routers
- update all cross-references in app, components, and docs
Made-with: Cursor
the runner's GITHUB_TOKEN (scoped to pullfrog/app) was leaking into
test subprocesses targeting pullfrog/test-repo, causing 400s from the
Pullfrog API on run-context fetches.
instead of deleting GITHUB_TOKEN from the subprocess env,
ensureGitHubToken now always mints a fresh OIDC token scoped to
GITHUB_REPOSITORY when OIDC is available — replacing any inherited
token with a correctly-scoped one.
also adds an informative throw in acquireTokenViaGitHubApp when
GITHUB_APP_ID/GITHUB_PRIVATE_KEY are missing.
Made-with: Cursor
* add Summarize mode for updatable PR summary comments
Introduces a Summarize mode that manages a single summary comment per PR,
updated in place on subsequent pushes. Mirrors the Plan/PlanEdit pattern:
API endpoint for existing-comment lookup at select_mode time, node ID
tracking on WorkflowRun, and SummaryUpdate guidance for edits.
Also fixes summary format instructions: Before/After uses inline <br/>
to avoid double line breaks, metadata line placed after key changes,
SHA-256 anchor instructions strengthened against fabrication.
Made-with: Cursor
* fix pre-existing lint error in checkout.ts
Made-with: Cursor
* fix dead restricted param in deepenForBeforeSha
GitAuthOptions dropped the restricted field in the ASKPASS refactor (#478)
but deepenForBeforeSha (#471) still passed it. Remove the field and the
now-unused shell param from DeepenForBeforeShaParams.
Made-with: Cursor
* agent & model refactor with ASKPASS git auth, UI restructure, clerk v7
Made-with: Cursor
* fix stale agent/effort refs, add tests for askpass + model resolution
- reviewCleanup.ts: payload.agent -> payload.model, remove effort
- selectMode.ts PlanEdit: remove delegation/subagent/effort references
- pullfrog.yml.ts: update env vars (drop GOOGLE_API_KEY/CURSOR_API_KEY,
add GOOGLE_GENERATIVE_AI_API_KEY/XAI_API_KEY/MOONSHOT_API_KEY/OPENCODE_API_KEY)
- FlagsSettings/RepoInstructionsSection: remove stale effort/timeout copy
- new: gitAuthServer.test.ts (10 tests — lifecycle, token delivery, tamper detection, script gen)
- new: agent.test.ts (4 tests — default opentoad, AGENT_OVERRIDE, invalid override)
- new: models.test.ts (19 tests — parseModel, resolution, registry invariants)
- update models.dev snapshot
Made-with: Cursor
* fix changed-agents.sh to filter legacy agent files from CI matrix
legacy agent files (claude.ts, codex.ts, etc.) are @ts-nocheck and not
exported from index.ts. changed-agents.sh now reads index.ts imports to
build the active agent set and treats changes to inactive files as
non-agent changes (opentoad canary only).
Made-with: Cursor
* remove MCP file tools, old agent harnesses, and obsolete security tests
ASKPASS-based git auth makes the old MCP file tool security layer unnecessary:
- token never in subprocess env, so symlink/gitattributes/hook attacks can't exfiltrate it
- agents now use native file tools (OpenCode builtin read/edit)
deleted:
- action/mcp/file.ts (file_read, file_write, file_edit, file_delete, list_directory)
- action/mcp/index.ts (dead re-export)
- agent harnesses: claude.ts, codex.ts, cursor.ts, gemini.ts, opencode.ts
- opencode-runner.ts (inlined into opentoad.ts)
- security tests that validated MCP file tool restrictions
- commented-out three-step review flow (~300 lines)
- sanitizeSchema/wrapSchema dead code from mcp/shared.ts
- OPENCODE_MODEL_MINI/MAX env vars (effort-level model overrides removed)
updated test prompts to use generic file ops instead of MCP tool names.
restored pkg-json-scripts + requirements-txt-attack (test --ignore-scripts defense).
Made-with: Cursor
* bump actions/checkout v4 → v6 (node 24)
node 20 actions deprecated june 2, 2026.
Made-with: Cursor
* temporarily disable fail-fast on agnostic tests to debug checkout@v6
Made-with: Cursor
* re-enable fail-fast on agnostic tests
Made-with: Cursor
* fix test token mismatch: mint OIDC tokens scoped to target repo
CI tests override GITHUB_REPOSITORY to pullfrog/test-repo but inherit
the runner's GITHUB_TOKEN (scoped to pullfrog/app), causing 401s on
every run-context fetch. Clear GITHUB_TOKEN in the test subprocess so
ensureGitHubToken() mints a properly scoped token via OIDC.
Also centralizes the default GITHUB_REPOSITORY in runAgentStreaming
instead of repeating it in every test file, and fixes preview-cleanup
to remove workers from all queues (not just name-matching ones).
Made-with: Cursor
* fix ensureGitHubToken to try OIDC when app credentials are absent
ensureGitHubToken only attempted token minting when GITHUB_APP_ID and
GITHUB_PRIVATE_KEY were set. In CI, OIDC is available but app creds
aren't exposed — so the guard prevented minting entirely.
Made-with: Cursor
* dead code cleanup: remove remnants of deleted agents, file tools, effort system
remove unused @anthropic-ai/claude-agent-sdk and @openai/codex-sdk deps,
orphaned file-tool security tests, dead GEMINI_MODEL passthrough, stale
opencode-runner wiki refs, deleted test file references, and MCP file tool
docs. rename docs/effort → docs/models. fix vitest setup: move dotenv to
globalSetup (runs once before forks instead of per-file, 19s → 200ms).
Made-with: Cursor
* address review feedback: remove dead code, update stale references
- remove AGENT_OVERRIDE (only opentoad exists)
- remove shellToolName plumbing (always restricted shell)
- bump action version to 0.0.179
- remove CURSOR_API_KEY from all workflows/configs
- remove OPENCODE_MODEL_MINI/MAX from workflows/docs
- delete wiki/effort.md, rewrite docs/effort.mdx as "Models"
- rewrite wiki/modes.md: orchestrator/subagent → single agent
- simplify flag system: drop builtin flag extraction (debug, effort,
timeout, agent), keep custom flag replacement only
- reserve all legacy flag names to prevent custom flag conflicts
Made-with: Cursor
* regenerate lockfile after removing claude-agent-sdk and codex-sdk
Made-with: Cursor
* fix import ordering, add lockfile check to pre-push hook
Made-with: Cursor
* remove dead debug payload field, stale packageExtensions
Made-with: Cursor
* merge proc-sandbox and token-exfil into a single test
proc-sandbox and token-exfil were duplicative — both tested that
SANDBOX_TEST_TOKEN couldn't be exfiltrated. consolidated into
token-exfil with shell:restricted (which actually exercises filterEnv)
and the /proc attack vector hints from proc-sandbox.
Made-with: Cursor
* fix wiki adversarial.md to match actual tokenExfil validator
Made-with: Cursor
* fix bodyless review bug by using pending + submit flow
createReview with event:"COMMENT" publishes immediately, so the
subsequent updateReview (to add footer with Fix links) fails when
the agent omits the review-level body — GitHub rejects editing a
bodyless review. this left ghost reviews and caused retries with
a garbage body like `" "`.
switch to a two-phase flow: createReview without event (PENDING),
then submitReview with the full body + footer. single atomic
publish, no updateReview needed.
Made-with: Cursor
* support bodyless reviews — skip footer when no body provided
Made-with: Cursor
* early return for bodyless reviews
Made-with: Cursor
* extract submitAndCleanup and buildReviewFooter helpers
Made-with: Cursor
* fix: default approved to false for buildReviewFooter
Made-with: Cursor
* run action typecheck alongside root tsc
Made-with: Cursor
* refactor: extract submitReview helper, keep cleanup inline
Made-with: Cursor
* skip pending+submit for bodyless reviews — single createReview instead
Made-with: Cursor
* restore pre-existing comments
Made-with: Cursor
Agents were bypassing the git auth boundary by running `git pull/push`
through the shell tool (which has no credentials). This caused auth
failures and cascading issues (botched rebases, corrupted files).
- shell: block all git commands with error directing to dedicated tools
- push_branch: catch "fetch first" rejections with step-by-step recovery
- git tool: improve auth redirect errors with specific tool guidance
Made-with: Cursor
When delegate() runs multiple subagents in parallel, their logs
interleave without visual distinction. Use AsyncLocalStorage to
automatically prefix every log line inside runSubagent() with the
task label in magenta (e.g. [frontend-review]).
Co-authored-by: Cursor <cursoragent@cursor.com>
the sudo-unshare sandbox path runs the entire command as root, causing
files modified by shell commands (e.g. git merge) to become root-owned.
this breaks file_write/file_edit which run in the Node.js parent process
as the normal user (EACCES errors).
after PROC_CLEANUP (which needs root for umount/mount), drop back to
the original user via `su -p` so file operations match the uid of the
parent process. security-neutral: PID namespace isolation is the barrier,
not privilege level inside it.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add node24 binary directory to PATH in action entry point so spawned
processes (pnpm, npm, etc.) resolve to the correct node version instead
of the runner's default v20. Improve delegate task result logging with
success/failure status and summaries. Use collapsible log groups for
dependency install output instead of raw streaming.
Co-authored-by: Cursor <cursoragent@cursor.com>
Standardize orchestrator/subagent instructions on the MCP shell tool and format mode guidance as explicit checklists to make delegation flows easier to follow. Bump the action package version to 0.0.171 for this release.
Co-authored-by: Cursor <cursoragent@cursor.com>