Skip to content

fix(pr-agent): fork gate for /commands, and AI_TIMEOUT under its own step cap - #41

Merged
yakimoto merged 4 commits into
mainfrom
fix/418-fork-gate-and-ai-timeout
Aug 24, 2026
Merged

fix(pr-agent): fork gate for /commands, and AI_TIMEOUT under its own step cap#41
yakimoto merged 4 commits into
mainfrom
fix/418-fork-gate-and-ai-timeout

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

User description

This repo merged the inline pr-agent lane before two defects in it were found. The 16 repos whose adoption PRs are still open were re-synced in place; this one had already merged, so it needs its own PR.

Source of truth: wave-av/wave-foundation-public#73. Findings tracked as wave-pen#418; the fan-out wave as wave-pen#417.

1. Forks were unchecked on the issue_comment arm — and not by omission

The job-level if: refuses forks on pull_request via head.repo.fork == false. The issue_comment arm carried no such check, while the header comment claimed "Forks skipped (no secrets there)" — true of one arm, false of the other.

The reason it was missing is structural. Fork status is not in an issue_comment payload. Measured, with a positive control so the absence is a measurement and not a guess:

$ gh api repos/wave-av/<repo>/issues/47 --jq ".pull_request | keys"
["diff_url","html_url","merged_at","patch_url","url"]

$ gh api repos/wave-av/<repo>/pulls/47 --jq ".head.repo.fork"
false

Five URLs. No head, no repo. There was never an expression to write — so the check moves to a fork gate step that asks the pulls endpoint, which does carry it.

It fails closed. Only a literal false yields fork=false; everything else skips. Each branch was driven against a stubbed gh, not reasoned about:

gh returns meaning result
false same-repo PR fork=false — proceed
true fork PR fork=true — skip, warn
exit 1 / 404 token revoked, PR gone fork=true — skip
empty rate limit, network fork=true — skip
null fork deleted after the PR opened fork=true — skip

"I could not tell" must not reach the same answer as "not a fork" on the arm that carries OPENAI_KEY. The cost of erring this way is one skipped advisory review.

Severity, stated precisely rather than inflated

This lane runs no actions/checkout. Fork code is never fetched or executed, so there was no exfiltration path. What a /review on a fork PR actually reaches is the fork's diff, sent to the LLM router on our key — cost surface, already narrowed by the author_association allowlist.

So this is defence in depth. The durable risk was the comment, not the missing check: it told the next editor the guard was already there, and the day someone adds a checkout step to this lane, that belief is what would make it real.

2. CONFIG__AI_TIMEOUT was 600s inside a 360s step — in both env blocks

Unreachable by construction. The runner killed the step first, so pr-agent never reached its own timeout, never fell back to CONFIG__FALLBACK_MODELS, and returned no error the retry could classify. It also undercut the per-attempt classifier, which reasons about STEP_BUDGET_S: "360" — a budget the AI layer inside the step did not respect.

Now 300: 60s of headroom under the cap, and above both observed successful reviews (64s, 180s).

3. A latent classifier bug the gate exposed — fixed at the root

stamp attempt 2 end carries if: always(), so it fires even when attempt 2 never ran, and END - ${START:-0} then subtracted from zero. Running the unmodified classifier against that state:

::warning::pr-agent TIMED OUT — the longest attempt ran 1787580408s against a 360s per-attempt budget

A 56-year attempt, reported as a confident diagnosis. Fixed in the arithmetic rather than by special-casing the caller, and the verdict gains an explicit skipped branch so a gated skip is not misread as "failed after 2 attempts".

Receipts

  • actionlint clean · zizmor --persona=regular clean · both new run: blocks shellcheck clean.
  • Classifier executed old-vs-new across five states. The skipped path goes 56-year-timeout → a correct notice; success, cancelled, never-ran, real-double-failure, and a genuine 350s timeout are all byte-identical between old and new.
  • Every event-derived value crosses into the shell through env:, never ${{ }} in a script body.
  • Already proven in the fleet: wave-av/api-spec merged this exact file and its main is byte-identical to the template.

The job id stays pr_agent, so the check-run context is unchanged and no branch protection rule needs touching.

Refs wave-pen#418, wave-pen#417, wave-pen#388


Note

Medium Risk
Touches a secret-bearing GitHub Actions lane (OPENAI_KEY, PR write) and fork/concurrency gating. Fail-closed skips are the main behavioral change; there is still no checkout of fork code.

Overview
Fixes three defects in the inline pr-agent lane so slash-command reviews no longer share lanes with issues, spend the LLM key on forks, or mis-report hangs.

Forks and concurrency. issue_comment cannot see head.repo.fork, so a new fail-closed fork gate step queries the pulls API and only runs the agent on an explicit false. Concurrency groups now distinguish PR vs issue so a comment on Issue #N cannot cancel a review of PR #N.

Timeouts and verdicts. CONFIG__AI_TIMEOUT drops from 600s to 300s so it sits under the 6-minute step (fallback models can actually fire). Verdict timing is per-attempt, not wall-clock across retries, and skipped/gated runs are classified as notices instead of fake TIMED OUT. Maintainer /review on a fork PR is now declined with a warning.

Reviewed by Cursor Bugbot for commit 087ab9b. Bugbot is set up for automated code reviews on this repo. Configure here.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Review in cubic


PR Type

Bug fix, Enhancement


Description

  • Added fork gate to handle issue_comment events properly

  • Adjusted CONFIG__AI_TIMEOUT from 600 to 300 seconds

  • Fixed attempt duration calculation to avoid false timeouts

  • Updated changelog with detailed fix documentation


Diagram Walkthrough

flowchart LR
  A["Fork gate step"] --> B["AI timeout adjustment"]
  A --> C["Attempt duration fix"]
  B --> D["Security enhancement"]
  C --> E["Reliability improvement"]
Loading

File Walkthrough

Relevant files
Bug fix
pr-agent.yml
Enhanced security and reliability in pr-agent workflow     

.github/workflows/pr-agent.yml

  • Added fork gate step to handle issue_comment events
  • Adjusted CONFIG__AI_TIMEOUT to 300 seconds
  • Fixed attempt duration calculation logic
  • Added detailed warning messages for forked PRs
+169/-10
Documentation
CHANGELOG.md
Updated changelog with detailed fix documentation               

CHANGELOG.md

  • Documented fork handling fix
  • Noted AI timeout adjustment
  • Added detailed defect resolution information
  • Updated Unreleased section with fix details
+26/-0   

…step cap

This repo merged the inline pr-agent lane before two defects in it were found.
The 16 repos whose adoption PRs are still open were re-synced in place; this one
already merged, so it needs its own PR. Source of truth: wave-foundation-public#73.

1. Fork status is now RESOLVED for slash commands, not assumed. The job-level
   `if:` refuses forks on the `pull_request` arm; it structurally cannot on
   `issue_comment`, because fork status is absent from that payload — measured,
   with a positive control: `issues/<n>.pull_request` carries exactly [diff_url,
   html_url, merged_at, patch_url, url], while `pulls/<n>.head.repo.fork`
   answers. A `fork gate` step asks the pulls endpoint and FAILS CLOSED: only a
   literal `false` proceeds; a 404, a revoked token, a rate limit and
   `.head.repo = null` (fork deleted after the PR opened) all skip.

   Scope, stated rather than inflated: this lane runs no `actions/checkout`, so
   fork code is never fetched or executed and no exfiltration path existed. What
   a /review on a fork PR reaches is the fork diff, sent to the LLM router on
   our key — cost surface, already narrowed by the author_association allowlist.
   The durable defect was the COMMENT claiming "Forks skipped (no secrets
   there)": true of one arm, false of the other, and exactly what would mislead
   whoever adds a checkout step later.

2. CONFIG__AI_TIMEOUT 600 -> 300, in both env blocks. A 600s AI budget inside a
   360s step is unreachable: the runner killed the step first, so pr-agent never
   reached its own timeout, never fell back to CONFIG__FALLBACK_MODELS, and
   returned no error the retry could classify.

3. A latent classifier bug the gate exposed. `stamp attempt 2 end` runs under
   `if: always()`, so when attempt 2 never ran the arithmetic subtracted from
   ZERO and reported a 1787580408-second attempt as a confident TIMED OUT.
   Fixed at the arithmetic rather than by special-casing the caller; the verdict
   also gains an explicit `skipped` branch.

The job id stays `pr_agent`, so the check-run context is unchanged and no branch
protection rule needs touching.

Refs wave-pen#418, wave-pen#417, wave-pen#388

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codeant-ai

codeant-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 0659b67 Aug 24, 2026 · 14:16 14:17

@cursor

cursor Bot commented Aug 24, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9ef394b0-18cf-45ed-a958-d72f3c7adbb3)

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 26 minutes.

View limit details

Limit details: You’ve used the included review currently available. Your 91 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4ddcf43a-c674-40a8-a717-00272ba7a79c

📥 Commits

Reviewing files that changed from the base of the PR and between 438281f and 087ab9b.

📒 Files selected for processing (2)
  • .github/workflows/pr-agent.yml
  • CHANGELOG.md

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 24, 2026
@cubic-dev-ai

cubic-dev-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Running ultrareview automatically — This PR implements a security-critical 'fork gate' to protect secrets within CI/CD infrastructure and refines sensitive timeout logic using shell-scripted arithmetic that is prone to subtle failures.. I'll post findings when complete.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix pr-agent fork gating for /commands and align AI timeout with step budget

🐞 Bug fix ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add explicit fork gate for issue_comment /commands by resolving PR fork status via GitHub API.
• Reduce CONFIG__AI_TIMEOUT to stay under the 360s step timeout, enabling fallback/retry behavior.
• Fix retry verdict timing to measure per-attempt duration and correctly classify timeouts vs
 errors.
Diagram

graph TD
  A(["GitHub event"]) --> B["pr-agent job"] --> C{"Fork gate"} --> D["PR-Agent attempt 1"] --> E["Backoff + retry"] --> F["PR-Agent attempt 2"] --> G["Verdict classifier"]
  C --> H[("GitHub Pulls API")]
  D --> I{{"LLM router"}}
  F --> I
  subgraph Legend
    direction LR
    _job["Workflow/job"] ~~~ _dec{"Decision"} ~~~ _api[("API")] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use actions/github-script instead of gh CLI for fork resolution
  • ➕ Removes dependency on gh CLI behavior/output formatting
  • ➕ Easier to handle API errors explicitly and return structured outputs
  • ➖ More verbose JS embedded in workflow YAML
  • ➖ Requires care to keep token usage/scopes identical and handle pagination/edge cases
2. Eliminate issue_comment arm; accept only pull_request triggers
  • ➕ Simplifies security model and gating (fork status available in payload)
  • ➕ Fewer edge cases around comment-triggered context
  • ➖ Loses /command UX on PR comments (primary feature)
  • ➖ Would likely require a different command mechanism (e.g., labels)

Recommendation: Current approach (a dedicated fail-closed fork gate step for issue_comment) is the best fit because fork status is structurally unavailable in the issue_comment payload. The step-level guard keeps secrets exposure constrained without changing event types, and it preserves the /command workflow while making the prior comment/assumption accurate.

Files changed (1) +144 / -9

Bug fix (1) +144 / -9
pr-agent.ymlAdd fail-closed fork gate, fix per-attempt timing, and lower AI timeout +144/-9

Add fail-closed fork gate, fix per-attempt timing, and lower AI timeout

• Introduces a dedicated "fork gate" step for issue_comment-triggered slash commands by querying the Pulls API and skipping runs unless fork is explicitly false. Lowers CONFIG__AI_TIMEOUT from 600s to 300s so pr-agent timeouts occur before the 360s step kill, enabling fallback/retry classification. Refactors timestamping and verdict arithmetic to be per-attempt (including retry) and correctly distinguish skipped, timed-out, and error outcomes.

.github/workflows/pr-agent.yml

@macroscopeapp

macroscopeapp Bot commented Aug 24, 2026

Copy link
Copy Markdown

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR materially changes a secret-bearing GitHub Actions review lane by adding fork/API gating, separating concurrency lanes, and altering timeout and retry classification. Although the gate fails closed and the changes are documented, the new execution gate and security/cost implications warrant human review.

Not approved because:

  • Credit balance exhausted. Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

@gitar-bot

gitar-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

Note

Automatic reviews are paused because your team has used its included automatic processing for this billing period (headroom scales with your seat count). You can still comment "Gitar review" to run one anytime, and automatic reviews resume on their own by September 1. Add seats for more headroom.
Learn more

Code Review ✅ Approved

Adds a fork gate to block unsafe PR-agent runs on forked pull requests, aligns the AI timeout to fit within the step budget, and fixes a classifier arithmetic bug that caused false timeout reports.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

I can't run this ultrareview because your workspace has reached its monthly review limit. cubic has reviewed 100,145 of the 100,000 allowed lines of code this month. Reviews resume on 4 September 2026 (in 12 days). Enable flex capacity to cover overages automatically and resume reviews now. Learn how flex capacity works.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

@qodo-code-review

qodo-code-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. pr-agent changes not in changelog ✓ Resolved 📘 Rule violation § Compliance
Description
This PR changes user-visible PR-agent behavior (fork /commands are now refused and
CONFIG__AI_TIMEOUT behavior changes), but CHANGELOG.md has no Unreleased entry documenting it.
This can surprise contributors/maintainers relying on slash commands and CI review behavior.
Code

.github/workflows/pr-agent.yml[R155-158]

      - name: PR-Agent (OSS qodo-merge)
        id: agent
+        if: steps.gate.outputs.fork != 'true'
        # STEP-level budget, under the job's 15 (wave-pen#386).
Evidence
PR Compliance ID 2497952 requires documenting user-facing behavior changes under ## [Unreleased]
in the root CHANGELOG.md. The workflow changes alter how slash commands run (forks now skipped via
a fork gate) and adjust CONFIG__AI_TIMEOUT, while CHANGELOG.md contains only the `##
[Unreleased]` header and no entries.

Rule 2497952: Document user-facing changes in Unreleased section of CHANGELOG.md
.github/workflows/pr-agent.yml[155-158]
.github/workflows/pr-agent.yml[180-185]
CHANGELOG.md[1-7]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR introduces user-facing behavior changes in the PR-agent workflow, but the root `CHANGELOG.md` has no corresponding bullet under `## [Unreleased]`.

## Issue Context
Key visible changes include refusing fork-triggered `/commands` (via the new fork gate) and changing the effective AI timeout budget.

## Fix Focus Areas
- CHANGELOG.md[1-7]
- .github/workflows/pr-agent.yml[155-158]
- .github/workflows/pr-agent.yml[180-185]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 1 rule
✅ REVIEW.md
Review mode: ⚖️ Balanced

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread .github/workflows/pr-agent.yml
@bito-code-review

Copy link
Copy Markdown

The pull request introduces significant changes to the PR-agent workflow that should be documented in the CHANGELOG.md file under the [Unreleased] section. Specifically, the following changes are missing from the changelog:

  • The implementation of a fork gate that refuses /commands from forks in the issue_comment workflow.
  • The modification of the AI timeout budget calculation, which now tracks time per-attempt rather than per-job.

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

🍒 Ready to be cherry-picked — ✅ Merged (0) · ☑ Fixed (1)

Grey Divider

🔗 Fix PR: #42

This fix PR was closed automatically. Its branch is preserved so you can cherry pick the changes into the original PR.

Prompt for coding agent

This is an automated fix prepared on a separate branch (#42). It is NOT applied to this PR.
To use it: review Fix PR #42 (https://github.com/wave-av/wave-modules/pull/42), evaluate each change critically against your local context, and cherry-pick the changes that are correct into this branch. Do not accept them blindly.
Process — 1 fixed
  • ☑ Fixed: pr-agent changes not in changelog

…ce of a true

Review of this wave found the fail-closed gate had a fail-OPEN consumer. Two
reviewers flagged it independently, on two different repos, and they were right.

    if: steps.gate.outputs.fork != 'true'      # grants when the output is EMPTY

The gate could only fail closed if it always wrote an output. It did, on every
path — so this did not fail open today, and the implicit success() on the
consumer covers a gate that errors outright. But the safety rested on an
argument rather than on the structure, and it is the very argument this change
exists to delete: absence must not read as permission.

Two independent changes, so neither carries the invariant alone:

  - the gate now assigns a shell variable that STARTS at `true` and writes ONCE
    at the end, so no future edit adding an early exit can emit nothing;
  - the consumer requires `== 'false'`, an explicit affirmative, so an empty or
    missing output skips the agent.

Also braces both sides of the A2 subtraction in the verdict step. The bare
`ATTEMPT2_START` was CORRECT — POSIX arithmetic expansion evaluates a bare name
as a variable, verified identical (180 == 180) — but a reviewer read it as a
literal token and filed it High. An expression that reads wrong on 27 repos gets
re-filed on 27 repos, so it is normalised rather than defended.

RECEIPTS. actionlint clean; zizmor clean; shellcheck clean. The gate was driven
through all six branches plus the reviewers' no-output scenario: only a literal
`false` reaches AGENT RUNS. The verdict was re-run across all six states and is
unchanged on the five that already worked.

LIVE: wave-av/api-spec merged the previous revision and its pull_request run
executed `fork gate (issue_comment only) -> success` in production, then ran the
agent — so the gate does not wrongly refuse a legitimate same-repo PR.

Upstream: wave-av/wave-foundation-public#73. Refs wave-pen#418, wave-pen#417.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 24, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_8b246f15-da38-47cd-aa1f-57aa717d5db2)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @yakimoto, you have reached your weekly rate limit of 250000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

Hardens the pr-agent workflow by adding a fail-closed API-based fork gate for slash-command reviews, reducing the AI timeout to fit the step cap, and fixing per-attempt duration and skipped-run classification without changing the pr_agent check context.

Sequence diagram for the fail-closed pr-agent fork gate

sequenceDiagram
    participant GitHub as GitHub Actions
    participant Gate as fork gate
    participant API as GitHub pulls API
    participant Agent as PR-Agent

    GitHub->>Gate: Evaluate event and initialize fork=true
    alt pull_request event
        Gate->>Gate: Set fork=false from job-level guard
    else issue_comment event
        Gate->>API: gh api repos/REPO/pulls/PR_NUMBER
        API-->>Gate: .head.repo.fork
        alt response is false
            Gate->>Gate: Set fork=false
        else response is true or unreadable
            Gate->>Gate: Keep fork=true and warn
        end
    end
    Gate-->>Agent: fork output
    alt fork == false
        Agent->>Agent: Run with OPENAI_KEY
    else fork == true
        Agent-->>GitHub: Skip agent and classify as skipped
    end
Loading

State diagram for pr-agent verdict outcomes

stateDiagram-v2
    [*] --> Gate
    Gate --> Skipped: fork output is true
    Gate --> Attempt1: fork output is false
    Attempt1 --> Success: success
    Attempt1 --> Attempt2: failure
    Attempt2 --> Success: success
    Attempt2 --> TimedOut: longest attempt reaches budget minus slack
    Attempt2 --> Failed: both attempts fail below budget
    Skipped --> [*]
    Success --> [*]
    TimedOut --> [*]
    Failed --> [*]
Loading

Flow diagram for per-attempt timeout classification

flowchart LR
    A["Stamp attempt 1 start"] --> B["PR-Agent attempt 1\nAI timeout 300s"]
    B --> C["Stamp attempt 1 end"]
    C --> D{"Attempt 1 failed?"}
    D -- No --> E["Classify outcome"]
    D -- Yes --> F["Backoff 45s"]
    F --> G["Stamp attempt 2 start"]
    G --> H["PR-Agent retry\nAI timeout 300s"]
    H --> I["Stamp attempt 2 end"]
    I --> E
    E --> J["Compute A1 and A2"]
    J --> K["Compare longest attempt\nwith STEP_BUDGET_S - 15"]
Loading

File-Level Changes

Change Details Files
Added a fail-closed fork gate for the issue-comment trigger before any secret-bearing agent execution.
  • Queries the pulls API for fork status because issue-comment payloads lack repository metadata.
  • Defaults to skipping unless the API returns a literal false, with warnings for forks and indeterminate responses.
  • Requires an explicit non-fork output before either agent attempt can run.
.github/workflows/pr-agent.yml
Aligned both agent attempts with the workflow step budget.
  • Reduced CONFIG__AI_TIMEOUT from 600 seconds to 300 seconds in both environment blocks.
  • Preserved fallback-model and retry behavior by leaving headroom below the 360-second step cap.
.github/workflows/pr-agent.yml
Reworked retry outcome classification to measure attempts independently and handle gated runs correctly.
  • Records start and end timestamps for each attempt, including timeout-safe end stamps.
  • Classifies only the longest actual attempt against the per-attempt budget and guards missing attempt-2 timestamps.
  • Adds an explicit skipped verdict and retains existing success, cancellation, and failure handling.
.github/workflows/pr-agent.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

…mber space

Review found a SECOND concurrency collision, on a different axis from the one
this template already documents (wave-pen#386).

`issue_comment` fires for ISSUES as well as PRs, and GitHub draws both from ONE
number sequence. So a comment on Issue #30 and a `/review` on PR #30 entered the
same concurrency group. Concurrency is evaluated at WORKFLOW level, BEFORE the
job-level `if:` runs — so the Issue comment cancelled the PR review already in
flight, and was then skipped itself, having done nothing.

That is the identical shape as the #386 defect the block above exists to fix,
one axis over: a run that will not review taking the lane from the run that
would have. #386 separated the two EVENTS; it did not separate the two number
spaces inside one event.

    pull_request        PR 433   -> pr-agent-pull_request-pr-433
    issue_comment on PR  30      -> pr-agent-issue_comment-pr-30
    issue_comment on ISSUE 30    -> pr-agent-issue_comment-issue-30

The last two used to be one group. actionlint and zizmor clean.

Upstream: wave-av/wave-foundation-public#73. Refs wave-pen#418, wave-pen#417.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 24, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_832244e1-5033-4430-86e5-85eac920df2f)

A reviewer flagged the missing entry on wave-modules#41. 25 of the 28 repos in
this wave keep the same Keep-a-Changelog convention, so the entry lands in all
of them rather than only the repo whose review happened to catch it — fixing the
reported instance and leaving the class is the pattern this wave keeps undoing.

The change IS user-visible, which is why it belongs here: a maintainer's
`/review` on a fork PR is now declined with a warning instead of silently
running, so contributors on forks see different behaviour.

Refs wave-pen#418, wave-av/wave-foundation-public#73

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 24, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_7c19ab8f-863c-4c69-a0e0-edfbf729aa3f)

@yakimoto

Copy link
Copy Markdown
Contributor Author

Review disposition — every finding on this PR, actioned or refuted with evidence

This branch has been updated. Reviews across this wave raised nine distinct findings; four were real and are fixed, three are working-as-intended, two are refuted with measurements. Nothing was silently skipped.

Source of truth for the template: wave-av/wave-foundation-public#73. Tracked as wave-pen#418.

Fixed — the reviewers were right

finding disposition
Fork gate fails openif: … != 'true' grants on an empty output Accepted. The gate wrote an output on every path, and a gate step that errors is caught by the consumer's implicit success(), so it did not fail open in practice — but the safety rested on an argument rather than the structure. The gate now writes once, from a variable that starts at the refusing value, and the consumer requires == 'false'. Two independent changes, so neither carries the invariant alone.
issue_comment has no fork check / secrets on fork PRs Accepted, and the cause is structural. Fork status is absent from an issue_comment payload — issues/<n>.pull_request carries exactly [diff_url, html_url, merged_at, patch_url, url], while pulls/<n>.head.repo.fork answers. A fork gate step now asks the pulls endpoint.
AI timeout exceeds step timeout Accepted. CONFIG__AI_TIMEOUT: "600" inside a 360s step is unreachable — the runner killed the step first, so pr-agent never fell back to CONFIG__FALLBACK_MODELS. Now 300, in both env blocks.
issue_comment concurrency collision (PR #N vs Issue #N) Accepted, and it is a genuinely separate axis from wave-pen#386. PRs and Issues share one number sequence, and concurrency is evaluated before the job if: — so a comment on Issue #30 cancelled a /review on PR #30 and then skipped itself. The key now carries a pr/issue discriminator.
Verdict ELAPSED is cumulative, not per-attempt Already fixed in wave-foundation-public#72, before this wave. Fixing the fork gate then exposed a fourth defect in the same step, see below.
Missing CHANGELOG entry Accepted — and widened. Raised on one repo; 25 of the 28 in this wave share the Keep-a-Changelog convention, so the entry landed in all 25. Fixing the reported instance and leaving the class is the pattern this wave keeps undoing.

Refuted — with the measurement, not an opinion

The-PR-Agent/pr-agent is an unverified org / possible typosquat. The best-reasoned finding here, and it inverts on checking. All three names are one repository:

$ gh api repos/qodo-ai/pr-agent   --jq .full_name    ->  The-PR-Agent/pr-agent
$ gh api repos/Codium-ai/pr-agent --jq .full_name    ->  The-PR-Agent/pr-agent
   stars=12688  created=2023-07-05  fork=false  parent=none
   description: "PR Agent: The Original Open-Source PR Reviewer. This project is not the Qodo free tier."

GitHub is following an org rename (Codium-ai → qodo-ai → The-PR-Agent) transparently. fork: false with no parent rules out a fork; 12.7k stars and a 2023 creation date rule out a fresh typosquat. The pinned SHA resolves to the same object through either name.

The suggested remedy would make things worse: qodo-ai/pr-agent is a stale name that resolves only via redirect, and a released org name can be re-registered by anyone. Pinning to the current name plus a commit SHA is the stronger position. Keeping as is.

A2 arithmetic is broken — bare ATTEMPT2_START is a literal token. Not so; POSIX arithmetic expansion evaluates a bare name as a variable:

$ ATTEMPT2_END=1000 ATTEMPT2_START=820 bash -c 'echo $(( ${ATTEMPT2_END:-0} - ATTEMPT2_START ))'
180
$ ATTEMPT2_END=1000 ATTEMPT2_START=820 bash -c 'echo $(( ${ATTEMPT2_END:-0} - ${ATTEMPT2_START:-0} ))'
180

Normalised anyway. An expression that reads wrong on 27 repos gets re-filed on 27 repos, so consistency is worth more than being right about it.

Committable suggestions lack contents: write. Not reproduced. Committable suggestions are GitHub ```suggestion blocks posted through the pull-requests API — the human clicks "Commit suggestion" and GitHub commits under their identity; the workflow never pushes. pull-requests: write is granted. Checked against a live run rather than argued: api-spec run `32733642988` ran with `Contents: read` and `commitable_code_suggestions: true`, concluded success, posted 3 comments, and its log contains no permission error (the one `403` substring match is inside a docker layer digest).

Working as intended

  • No actions/checkout. Deliberate, and load-bearing: it is exactly why the fork finding is defence-in-depth rather than a live exfiltration path. Fork code is never fetched or executed.
  • A neutral message with a success exit. pr-agent is an advisory reviewer — it annotates, it never gates correctness — so a flaked reviewer must not block a PR. That is wave-foundation-public#3128's whole point.
  • A gh api rate limit treated the same as a real fork. That is the fail-closed design. "I could not tell" must not reach the same answer as "not a fork" on the arm holding OPENAI_KEY; the cost of erring this way is one skipped advisory review.

One defect no reviewer found, surfaced by fixing the first

stamp attempt 2 end runs under if: always(), so when attempt 2 never ran the verdict subtracted from zero:

::warning::pr-agent TIMED OUT — the longest attempt ran 1787580408s against a 360s per-attempt budget

A 56-year attempt, stated as a confident diagnosis. Latent since #72; the fork gate is simply the first path that reaches it. Fixed at the arithmetic, and the verdict gained an explicit skipped branch.

Receipts

actionlint clean · zizmor --persona=regular clean · shellcheck clean on both new run: blocks. The gate was driven through all six branches plus the no-output scenario — only a literal false reaches AGENT RUNS. The verdict was executed old-vs-new across six states; the five that already worked are byte-identical. Live: wave-av/api-spec merged an earlier revision and its pull_request run executed fork gate (issue_comment only) -> success in production and then ran the agent, so the gate does not wrongly refuse a legitimate same-repo PR.

@yakimoto
yakimoto merged commit e2bab46 into main Aug 24, 2026
19 checks passed
@yakimoto
yakimoto deleted the fix/418-fork-gate-and-ai-timeout branch August 24, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant