ci: gate Rams reviews behind real UI churn - #111
Conversation
Rams was set to review every PR on every push, which burned the free plan's 30-review budget on backend-only changes. All local repos are now in "labeled rams only" mode, so nothing gets reviewed unless something applies the label. This applies it automatically, but only when the diff is actually UI: a new .svelte component, or >=20 changed lines across .svelte/.css/ app.html/tailwind config, excluding tests and e2e. Re-adding a label GitHub already has fires no `labeled` webhook, so pushes to an already-labelled PR cannot spend a second review. The workflow never removes the label for the same reason. Needs issues: write, not just pull-requests: write — the `rams` label does not exist yet and creating a repo label is an issues-scoped action. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
🚅 Deployed to the dub-rip-pr-111 environment in dub-rip
|
|
Warning Review limit reached
Next review available in: 52 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe PR adds a GitHub Actions workflow that detects meaningful UI changes in eligible pull requests. It counts changed UI lines and new Svelte components, records a summary, and applies the ChangesRams UI Gate
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant PullRequest
participant GitHubActions
participant GitHubAPI
PullRequest->>GitHubActions: Trigger eligible pull request workflow
GitHubActions->>GitHubAPI: Retrieve pull request files
GitHubAPI-->>GitHubActions: Return changed UI files and line counts
GitHubActions->>GitHubActions: Calculate UI metrics and thresholds
GitHubActions->>GitHubAPI: Create or apply rams label
GitHubActions-->>PullRequest: Publish workflow summary
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/rams-ui-gate.yml:
- Around line 60-63: Update the label setup command in the workflow so it
creates the shared rams label only when absent and does not overwrite existing
color or description metadata. Remove the --force behavior from the gh label
create invocation, or otherwise guard creation with an existence check while
preserving the current metadata for existing labels.
- Around line 34-43: Update the gh api invocation assigned to files to use
--slurp, then flatten the paginated response into a single file array before the
existing UI filtering in the ui jq pipeline. Preserve the current filename
filters and scalar new_components and ui_lines calculations so the summary
receives single numeric values.
- Around line 60-67: Update the mutating gh commands in the workflow step to
explicitly target "$REPO": add the repository option to both gh label create and
gh pr edit, while preserving their existing arguments and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 829e2c57-71b1-44f8-8026-f832b2ec4670
📒 Files selected for processing (1)
.github/workflows/rams-ui-gate.yml
| files=$(gh api "repos/$REPO/pulls/$PR/files" --paginate) | ||
|
|
||
| ui=$(jq -c ' | ||
| [ .[] | ||
| | select(.filename | test("\\.svelte$|\\.css$|^src/app\\.html$|^tailwind\\.config\\.[cm]?[jt]s$")) | ||
| | select(.filename | test("(^|/)(tests?|e2e)/|\\.(test|spec)\\.") | not) | ||
| ]' <<<"$files") | ||
|
|
||
| new_components=$(jq '[.[] | select(.status == "added" and (.filename | endswith(".svelte")))] | length' <<<"$ui") | ||
| ui_lines=$(jq '[.[].changes] | add // 0' <<<"$ui") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- workflow excerpt ---\n'
sed -n '1,120p' .github/workflows/rams-ui-gate.yml 2>/dev/null || true
printf '\n--- local version/tools ---\n'
(git --version 2>/dev/null | head -n1) || true
(gh version 2>/dev/null | head -n1) || true
(jq --version 2>/dev/null) || true
(node --version 2>/dev/null) || true
printf '\n--- inspect changed files if available ---\n'
git diff --name-only HEAD~1..HEAD 2>/dev/null | sed -n '1,120p' || trueRepository: jzstern/dub-rip
Length of output: 244
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- workflow excerpt ---'
sed -n '1,120p' .github/workflows/rams-ui-gate.yml 2>/dev/null || true
echo
echo '--- local version/tools ---'
git --version 2>/dev/null | head -n1 || true
gh version 2>/dev/null | head -n1 || true
jq --version 2>/dev/null || true
node --version 2>/dev/null || true
echo
echo '--- changed files if available ---'
git diff --name-only HEAD~1..HEAD 2>/dev/null | sed -n '1,120p' || trueRepository: jzstern/dub-rip
Length of output: 2866
🌐 Web query:
GitHub CLI gh api --slurp pagination each page output separate JSON value
💡 Result:
When using the GitHub CLI (gh api) with both --paginate and --slurp, the tool does not output each page as a separate, individual JSON value [1][2]. Instead, the --slurp flag instructs the CLI to wrap all individual pages (which are returned as JSON arrays or objects) into a single, outer JSON array [1][2]. In this combined mode: - Each individual page retrieved from the API is collected and placed as an element within the resulting master array [1][2]. - This effectively results in a single, valid JSON document containing all pages as items in that list [1][3][2]. If your workflow requires processing each page as a separate JSON value (for example, to pipe each page into a separate process or to handle them as discrete streams), you should avoid using --slurp [1]. Without --slurp, the GitHub CLI outputs each page sequentially as its own JSON document [1][2]. If you need to manipulate or flatten these pages while maintaining their individual integrity, you can use standard command-line tools like jq to process the stream without the wrapping array [3]. For further context on why this behavior exists, see the official gh api documentation [1] and related discussions in the GitHub CLI repository regarding how pagination and JSON formatting are handled [3][4].
Citations:
- 1: https://cli.github.com/manual/gh_api
- 2: https://github.com/cli/cli/blob/trunk/pkg/cmd/api/api.go
- 3: Allow
gh api--paginate --slurpto be used with--jqcli/cli#10459 - 4: Output a single JSON document from
api --paginatecli/cli#1268
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- jq behavior with newline-delimited arrays versus slurped array ---'
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
page1='[ {"filename":"src/add.css","status":"changed","changes":5}, {"filename":"src/foo.svelte","status":"changed","changes":3} ]'
page2='[ {"filename":"src/bar.css","status":"added","changes":7} ]'
echo 'without --slurp (default jq behavior)'
jq '[.[] | select(.filename | test("\\.svelte$|\\.css$|^src/app\\.html$|^tailwind\\.config\\.[cm]?[jt]s$"))]' <<< "$page1"
jq '[.[] | select(.filename | test("\\.svelte$|\\.css$|^src/app\\.html$|^tailwind\\.config\\.[cm]?[jt]s$"))]' <<< "$page2"
ui="$(jq '[.[] | select(.filename | test("\\.svelte$|\\.css$|^src/app\\.html$|^tailwind\\.config\\.[cm]?[jt]s$"))]' <<< "$page1"; jq '[.[] | select(.filename | test("\\.svelte$|\\.css$|^src/app\\.html$|^tailwind\\.config\\.[cm]?[jt]s$"))]' <<< "$page2")"
echo "ui=$(cat <<< "$ui")"
ui_lines="$(jq '[.[].changes] | add // 0' <<< "$ui")"
printf 'ui_lines=%s\n' "$ui_lines"
new_components="$(jq '[.[] | select(.status == "added" and (.filename | endswith(".svelte")))] | length' <<< "$ui")"
printf 'new_components=%s\n' "$new_components"
echo
echo 'with slurped wrapper array'
slurped='[{"filename":"src/add.css","status":"changed","changes":5}, {"filename":"src/foo.svelte","status":"changed","changes":3},{"filename":"src/bar.css","status":"added","changes":7}]'
ui_slurped="$(jq -c '[ .[][] | select(.filename | test("\\.svelte$|\\.css$|^src/app\\.html$|^tailwind\\.config\\.[cm]?[jt]s$")) ]' <<< "$slurped")"
ui_lines_slurped="$(jq '[.[].changes] | add // 0' <<< "$ui_slurped")"
new_components_slurped="$(jq '[.[] | select(.status == "added" and (.filename | endswith(".svelte")))] | length' <<< "$ui_slurped")"
printf 'ui_lines=%s\n' "$ui_lines_slurped"
printf 'new_components=%s\n' "$new_components_slurped"Repository: jzstern/dub-rip
Length of output: 900
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- correct slurped filter path ---'
slurped='[
[{"filename":"src/add.css","status":"changed","changes":5},{"filename":"src/foo.svelte","status":"changed","changes":3}],
[{"filename":"src/bar.css","status":"added","changes":7}]
]'
ui="$(jq -c '[ .[][] | select(.filename | test("\\.svelte$|\\.css$|^src/app\\.html$|^tailwind\\.config\\.[cm]?[jt]s$")) ]' <<< "$slurped")"
printf '%s\n' "$ui"
ui_lines="$(jq '[.[].changes] | add // 0' <<< "$ui")"
printf 'ui_lines=%s\n' "$ui_lines"
new_components="$(jq '[.[] | select(.status == "added" and (.filename | endswith(".svelte")))] | length' <<< "$ui")"
printf 'new_components=%s\n' "$new_components"Repository: jzstern/dub-rip
Length of output: 395
Flatten the paginated gh api --paginate response before comparing the counts.
Without --slurp, ui_lines and new_components receive newline-separated numbers from each page and the summary labels the PR from a non-scalar value, skipping the label-only path. Add --slurp and flatten the page arrays before UI filtering.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/rams-ui-gate.yml around lines 34 - 43, Update the gh api
invocation assigned to files to use --slurp, then flatten the paginated response
into a single file array before the existing UI filtering in the ui jq pipeline.
Preserve the current filename filters and scalar new_components and ui_lines
calculations so the summary receives single numeric values.
Two fixes from review: The job never checks out, so `gh label create` and `gh pr edit` had no git remote to infer the repository from and would have failed on the first qualifying UI PR. The passing run on #111 proves nothing here — it exits before both commands. Set GH_REPO so every gh call is scoped. `gh label create --force` rewrites an existing label's colour and description, so a qualifying PR could silently revert repo-managed metadata on the shared `rams` label. Create it only when absent. A third suggestion — that `gh api --paginate` needs `--slurp` or jq sees one document per page — was not applied. On gh 2.96.0 `--paginate` merges array responses: fetching an 11-file PR at per_page=2 yields a single 11-element array, which is what the jq filters already assume. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Description
Rams was configured to review every PR on every push, which spent the free plan's 30-review budget on backend-only changes — this repo's last few PRs (yt-dlp, PO tokens, health probes) touched no UI at all, and the budget is currently 30/30 exhausted.
All of my local repos are now set to "Labeled
ramsonly" in the Rams dashboard, so nothing is reviewed unless something applies the label. This workflow applies it automatically, but only when the diff is genuinely UI:.sveltecomponent, or.svelte/.css/src/app.html/tailwind.config.js…excluding
tests/,e2e/, and*.test.*/*.spec.*.Two properties worth calling out:
labeledwebhook, so only the first qualifying push triggers a review. The workflow also never removes the label — removing and re-adding would cost a second review.It needs
issues: writealongsidepull-requests: write— theramslabel doesn't exist in this repo yet, and creating a repo label is an issues-scoped action, sopull-requests: writealone would fail on the first run.Future work
The path regex is SvelteKit-specific. Rolling this out to the other repos now in label mode (
bcr,quorum,nocturne, …) needs per-stack globs.How to test
The gate logic was verified against this repo's real PR data plus synthetic diffs:
+page.sveltetweak.svelteundere2e/+page.svelteeditToast.svelte(6 lines)app.csschangeactionlintpasses clean (it shellchecks therun:block too).ramslabel+page.svelteby >20 lines — it should get theramslabel, and the run summary should show the counts that drove the decisionNote: the review budget resets around Aug 7, so until then a labelled PR will get "limit reached" from Rams rather than an actual review.
Author checklist
This PR:
🤖 Generated with Claude Code
Summary by CodeRabbit
Chores
User Impact