Skip to content

fix(source-control): stop Forgejo status refresh from scanning every pull request - #12223

Merged
juliusmarminge merged 3 commits into
pingdotgg:mainfrom
loispostula:fix/forgejo-status-pr-scan
Oct 10, 2026
Merged

juliusmarminge merged 3 commits into
pingdotgg:mainfrom
loispostula:fix/forgejo-status-pr-scan

Conversation

@loispostula

@loispostula loispostula commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

What Changed

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole repository and matching the branch client-side. The client-side match stays, since a server too old for the filter ignores it and answers unfiltered.

Why

vcs.refreshStatus hangs past 15s on any Forgejo or Gitea repository with real pull request history.

findLatestPrForHeadContext asks for the branch's pull requests with limit: 20. GitHub and GitLab narrow server-side; Forgejo did not, so it looped while (results.length < limit) over pulls?state=all&limit=50&page=N filtering in JS. A branch has one pull request at most, so the loop only stopped on an empty page, walking every pull request in the repository. That sits inside refreshStatus's remote write lock.

head is documented on GET /repos/{owner}/{repo}/pulls. With it, a branch with one pull request costs two requests, a branch with none costs one.

Introduced in #11436.

Checklist

  • This PR is small and focused
  • I explained what changed and why

Written with Claude Opus 5 in T3 Code.

rebased onto main at @juliusmarminge's request. updated the regression test for the extracted provider package.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Sep 17, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 17, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 62a3b56

Macroscope's review found this PR approvable — This is a localized one-line Forgejo bug fix that filters pull-request listings server-side, reducing unnecessary pagination while preserving the existing client-side filtering behavior. It introduces no schema, deployment, default-setting, or static-analysis changes.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

@loispostula
loispostula force-pushed the fix/forgejo-status-pr-scan branch from e657d8d to 8dae589 Compare September 17, 2026 08:59
@coderabbitai

coderabbitai Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 03d3597e-48ad-426c-8dc6-3c6e19f4d5a2


📥 Commits

Reviewing files that changed from the base of the PR and between 0150c6a and 8dae589.


📒 Files selected for processing (2)
  • apps/server/src/sourceControl/ForgejoSourceControlProvider.test.ts
  • apps/server/src/sourceControl/ForgejoSourceControlProvider.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.



📝 Walkthrough

Walkthrough

listChangeRequests now sends the source branch as an encoded Forgejo head query parameter. A new test verifies branch filtering, returned pull request numbers, and pagination termination after an empty page.

Changes

Forgejo branch filtering

Layer / File(s) Summary
Server-side branch filter and test
apps/server/src/sourceControl/ForgejoSourceControlProvider.ts, apps/server/src/sourceControl/ForgejoSourceControlProvider.test.ts
listChangeRequests adds the encoded source branch to the Forgejo request. The test verifies the query parameter, returned pull request, and two-call pagination behavior.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix


Merge Risk: ⚪ Minimal · up to 8dae5

The branch filter is encoded and retains the compatibility fallback; no merge-blocking issue remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description includes the required What Changed, Why, and Checklist sections. It clearly explains the Forgejo server-side filtering change, the performance problem, the compatibility fallback, and …
Title check ✅ Passed The title clearly describes the main change: preventing Forgejo status refreshes from scanning every pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR


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

@kossoy

kossoy commented Sep 23, 2026

Copy link
Copy Markdown

Confirming this on self-hosted Forgejo 16.0.5+gitea-1.22.0 with nightly 0.0.43-nightly.20260923.2150.

The repo has ~200 PRs. Two of them are huge merged PRs: a package store committed by mistake, then removed (~5.7M lines / ~31k files each). Forgejo computes additions/deletions/changed_files for every item in the list response without caching, so each of those two adds ~6.5 s to any listing page that contains it. The scan always walks to the empty page, so every PR lookup pays for both, even when the branch's PR is on page 1.

From the server trace:

  • vcs.refreshStatus: 22.75 s, of which findLatestPrForHeadContext is 21.6 s (4 pages; page 1 alone 15.6 s)
  • the same query with head=<branch>: 0.09 s, returns the same PR
  • head=owner:branch returns an empty list on this version. The bare branch name works, which is what this PR sends.

For us this is what raises the "Some requests are slow" toast when the window regains focus.

Moinax added a commit to Moinax/t3code that referenced this pull request Sep 27, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Sep 29, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Sep 30, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 1, 2026 — with ChatGPT Codex Connector
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 1, 2026 02:56

Dismissing prior approval to re-evaluate 8dae589

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 1, 2026
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 1, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 2, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 3, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 3, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Moinax

Moinax commented Oct 3, 2026

Copy link
Copy Markdown

Confirming this from outside the PR: I have been running this patch in a fork build since 2026-09-18, cherry-picked unchanged, against a self-hosted Forgejo. Three repositories there, the busiest with 126 pull requests tracked locally — exactly the "real pull request history" case, since that is what the old loop had to page through to answer a single branch. Two weeks of daily use, refreshStatus resolves immediately, and no behaviour difference anywhere else.

One thing I cannot report: the before/after on my own instance. I took the patch the day after this PR was opened, so I never ran the unfiltered loop against that Forgejo and have no timing to compare. What I can say is that head= is honoured by it, and that keeping the client-side match leaves the result identical when it is not.

Written with Claude Opus 5 (1M context) in T3 Code.

Moinax added a commit to Moinax/t3code that referenced this pull request Oct 3, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 3, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 4, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 5, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 5, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 6, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 6, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 6, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 6, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 7, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moinax added a commit to Moinax/t3code that referenced this pull request Oct 8, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@PaulHoza

PaulHoza commented Oct 8, 2026

Copy link
Copy Markdown

First: forgive me for pasting a mostly-agent written comment. I'm not a frequent GitHub-participator, so I'm outside my comfort zone here. I wanted to try to help with T3 Code, as it's helping me out so much.
Here ya go, pasted (with some editing) from an agent session where we figured out more about this issue:

Confirming this on self-hosted Forgejo 16.0.5 with nightly 0.0.46-nightly.20261008.2801. It caused real damage: on a 2-vCPU VPS, the unfiltered paging kept Forgejo at about one core, which helped drain the provider's CPU credits until the host was throttled to 20%.

Measured 2026-10-08:

  • The T3 server made 32–45 pulls?state=all&sort=recentupdate&limit=50&page=N requests per minute from one workstation. There are ~700 PRs, so lookups for branches with no PR, including every thread on main, walked 14+ pages each time. One ThreadPullRequestServiceV2.synchronize span took 125 s.
  • Forgejo 16.0.5 honors head= on this endpoint. pulls?state=all&head=main returns 0 items in one request (0.26 s), and head=<branch> returns exactly that branch's PRs. So this PR's approach removes the paging entirely here.
  • The 2-page cap in fix(server): Forgejo branch status no longer pages through every pull request #17201 would still cost two 50-PR pages per lookup on Forgejo. Its premise that the pulls API has no head filter doesn't hold on Forgejo 16.

Two related things that would also help:

  1. Skip the lookup for the repository's default branch.
  2. Forced refreshes fire on every run completion. In agent-heavy threads with many delegated runs, that multiplies the scans; a debounce would cut them.

Archiving and settling threads (#16762) reduced the load for us. Quitting T3 was the only way to stop it completely.

Moinax added a commit to Moinax/t3code that referenced this pull request Oct 9, 2026
…pull request

Pass head=<branch> to Forgejo's pulls endpoint instead of paging the whole
repository and matching the branch client-side. The client-side match stays,
since a server too old for the filter ignores it and answers unfiltered.

Cherry-picked from upstream pingdotgg#12223, still open there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@pat-s

pat-s commented Oct 10, 2026

Copy link
Copy Markdown

@juliusmarminge @flamboh @maria-rcks Sorry for pinging you all but this is a super severe bug. It can lead to full CPU usage on FJ hosts for 15min+.
Can this please be merged or patched otherwise?

loispostula and others added 2 commits October 10, 2026 18:06
…pull request

Forgejo listed pull requests unfiltered and matched the head branch in JS, so a branch with one pull request or none walked the repository's entire history 50 rows at a time until a page came back empty. Status refreshes run that under the remote write lock, which is why vcs.refreshStatus hung past 15s on repositories with any real pull request history.

Forgejo's pulls endpoint accepts a head parameter, so narrow server-side. The client-side branch match stays: a server too old for the filter ignores it and answers unfiltered.

Signed-off-by: Loïs Postula <lois@postu.la>
Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
@t3-code
t3-code Bot force-pushed the fix/forgejo-status-pr-scan branch from 8dae589 to eeaceb2 Compare October 10, 2026 18:09
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 10, 2026 18:09

Dismissing prior approval to re-evaluate eeaceb2

@juliusmarminge
juliusmarminge enabled auto-merge (squash) October 10, 2026 18:11
@juliusmarminge
juliusmarminge merged commit dacd2cb into pingdotgg:main Oct 10, 2026
29 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants