Skip to content

fix(gitea): share and cache the /pulls scan so card refreshes can't hammer a self-hosted forge - #8858

Merged
Jinwoo-H merged 3 commits into
stablyai:mainfrom
xianjianlf2:fix/gitea-pr-scan-share-cache-8807
Jul 15, 2026
Merged

Jinwoo-H merged 3 commits into
stablyai:mainfrom
xianjianlf2:fix/gitea-pr-scan-share-cache-8807

Conversation

@xianjianlf2

Copy link
Copy Markdown
Contributor

Summary

Fixes #8807.

Each worktree card resolved its branch to a PR by independently paginating the repo's full /pulls listing (state=all, up to MAX_PULL_REQUEST_PAGES = 5) with a 5s timeout. Gitea/Forgejo have no head-branch filter, a self-hosted Forgejo takes ~5s per page (reviewer data is loaded per PR), and a push event refreshes every card at once — one repo became hundreds of near-simultaneous requests whose responses were aborted right as they completed, and the burst OOM-killed the reporter's 512Mi Forgejo pod.

Fix

  • One shared scan per repo (pull-request-scan-cache.ts): concurrent branch lookups await the same in-flight page walk, and the result is cached for 30s. A burst that refreshes N worktree cards now costs a single page walk instead of N.
  • Negative results are cached too: a branch with no PR no longer rescans all 5 pages on every card refresh within the TTL.
  • List timeout raised to 15s (other requests keep the 5s cap), so slow-but-successful pages are used instead of discarded and retried — the reporter observed Orca cancelling right as Forgejo finished.
  • Cache invalidation on PR creation: after Orca creates a PR (and before the post-create findExistingPullRequest fallback), the scan is invalidated so the new PR is visible immediately rather than after the TTL.

The issue's remaining suggestion (state=open first with a lazy state=all fallback for merged detection) is noted as a possible follow-up; with the shared cache the marginal win is much smaller and it would double requests for merged-PR branches.

Testing

  • New tests: three concurrent branch lookups (hit, hit, miss) issue exactly one list request; repeat lookups within the TTL issue none.
  • vitest run src/main/gitea/ — 30 tests pass; npm run typecheck clean.

🤖 Generated with Claude Code

…ammer a self-hosted forge

Each worktree card resolved its branch by paginating the repo's full
/pulls listing (state=all, up to 5 pages) independently, with a 5s
timeout. Gitea/Forgejo have no head-branch filter, self-hosted Forgejo
takes ~5s per page (it loads reviewer data per PR), and a push event
refreshes every card at once — so one repo turned into hundreds of
near-simultaneous requests whose responses were aborted right as they
completed, and the burst OOM-killed a 512Mi Forgejo pod (stablyai#8807).

- Share one in-flight /pulls scan per repo across concurrent branch
  lookups and cache the result for 30s, so a burst costs a single page
  walk instead of one per worktree.
- Raise the list-scan timeout to 15s so slow-but-successful pages are
  used instead of discarded and retried.
- Invalidate the cached scan after Orca itself creates a PR (and before
  the post-create fallback lookup) so the new PR is seen immediately.

Fixes stablyai#8807

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: baaf17db-d0ca-4c3f-8871-1ab209db4dec

📥 Commits

Reviewing files that changed from the base of the PR and between 19f3d17 and 68616cd.

📒 Files selected for processing (2)
  • src/main/gitea/client.test.ts
  • src/main/gitea/pull-request-scan-cache.ts

📝 Walkthrough

Walkthrough

Adds a 30-second, repository-scoped cache for paginated Gitea pull-request scans, with concurrent request de-duplication, generation-based invalidation, bounded eviction, and reset support. Branch lookup uses the shared scanner with a longer list-request timeout. Pull-request creation flows invalidate cached scans before lookup and after successful creation. Tests cover cache reset, concurrent lookups, TTL reuse, failure retries, expiry, bounded size, and stale in-flight scans.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers summary and testing, but omits required Screenshots, AI Review Report, Security Audit, and Notes sections. Add the missing template sections, or explicitly mark Screenshots as 'No visual change' and include AI review, security, and notes details.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the shared/cached Gitea /pulls scan change and its impact on refresh bursts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e6a6fbb2-f8ba-4ac2-aa68-41d847276a05

📥 Commits

Reviewing files that changed from the base of the PR and between c12ade5 and fb1fde3.

📒 Files selected for processing (4)
  • src/main/gitea/client.test.ts
  • src/main/gitea/client.ts
  • src/main/gitea/pull-request-creation.ts
  • src/main/gitea/pull-request-scan-cache.ts

…e an invalidation

Review feedback: if a PR is created while a card-refresh scan is in
flight, the invalidation cleared the cache but the scan then finished
and re-cached the pre-create listing, hiding the new PR for a full TTL.
Track a per-repo generation: invalidation bumps it (and drops the
in-flight promise), and a scan only writes the cache when its generation
is still current.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@xianjianlf2

Copy link
Copy Markdown
Contributor Author

Fixed the invalidation race in the latest push: each repo key now carries a generation counter — invalidateGiteaPullRequestScan bumps it and drops the in-flight promise, and a scan only writes the cache if its generation is still current. Added a regression test that holds a scan in flight across an invalidation and asserts the post-create listing is what lands in the cache.

Co-authored-by: Orca <help@stably.ai>
@Jinwoo-H
Jinwoo-H merged commit fb5bc4f into stablyai:main Jul 15, 2026
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
…ammer a self-hosted forge (stablyai#8858)

* fix(gitea): share and cache the /pulls scan so card refreshes can't hammer a self-hosted forge

Each worktree card resolved its branch by paginating the repo's full
/pulls listing (state=all, up to 5 pages) independently, with a 5s
timeout. Gitea/Forgejo have no head-branch filter, self-hosted Forgejo
takes ~5s per page (it loads reviewer data per PR), and a push event
refreshes every card at once — so one repo turned into hundreds of
near-simultaneous requests whose responses were aborted right as they
completed, and the burst OOM-killed a 512Mi Forgejo pod (stablyai#8807).

- Share one in-flight /pulls scan per repo across concurrent branch
  lookups and cache the result for 30s, so a burst costs a single page
  walk instead of one per worktree.
- Raise the list-scan timeout to 15s so slow-but-successful pages are
  used instead of discarded and retried.
- Invalidate the cached scan after Orca itself creates a PR (and before
  the post-create fallback lookup) so the new PR is seen immediately.

Fixes stablyai#8807

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

* fix(gitea): don't let an in-flight scan re-cache a listing from before an invalidation

Review feedback: if a PR is created while a card-refresh scan is in
flight, the invalidation cleared the cache but the scan then finished
and re-cached the pre-create listing, hiding the new PR for a full TTL.
Track a per-repo generation: invalidation bumps it (and drops the
in-flight promise), and a scan only writes the cache when its generation
is still current.

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

* fix(gitea): bound PR scan cache and retry failures

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Bug report: worktree PR-status polling can hammer (and OOM) a self-hosted forge

3 participants