Summary
Add bundle size threshold checks to tuist inspect bundle via GitHub Check Runs. When a bundle is uploaded and a threshold is exceeded, the server posts a check run with an action_required conclusion and an “Accept” button directly in the GitHub PR UI. Clicking “Accept” flips the check to success — no CI re-run needed and no context switching to external dashboards. This lets teams catch bundle size regressions before merge while keeping the override workflow frictionless.
Motivation
Today, Tuist supports bundle size alerting via Slack notifications configured through the dashboard. While useful for monitoring trends on tracked branches, Slack alerts notify you only after a problematic merge but don’t block problematic changes.
Teams need a way to block PRs when a bundle size regression is detected. Key use cases from real customer feedback:
- Catch regressions before merge. Teams want to detect bundle size explosions on feature branches before they hit main, not after.
- Enforce budgets across all branches. Rather than configuring alerts per-branch, teams want a single threshold that applies to every
tuist inspect bundleinvocation in CI. - Non-blocking visibility with override escape hatch. Sometimes a significant bundle size increase is expected and intentional. The workflow should allow reviewers to acknowledge the increase rather than permanently blocking the PR.
Prior Art
tuist inspect dependencies --only implicit: Exits with a non-zero status when issues are found. This is the closest existing pattern in Tuist and the one users have explicitly referenced as the desired behavior for bundle size.- Webpack performance hints: Webpack supports
performance.maxAssetSizeandperformance.hints(set to"error") to fail builds when bundle sizes exceed thresholds. - Bundlesize / size-limit: Popular JS ecosystem tools that run in CI, compare against configured budgets, and post GitHub status checks.
- Emerge Tools status checks: Posts GitHub status checks that fail when size thresholds are exceeded. Uses an “Action Required” → “Accept” button pattern where developers acknowledge expected increases in the dashboard rather than modifying CI configuration.
- Android App Bundles: Google Play Console warns developers about APK size increases, but this happens post-upload rather than in CI.
Proposed Solution
Dedicated Bundle Thresholds Configuration
Introduce a new Bundles tab in the project settings, separate from the existing Notifications/Alerts system. This tab is purpose-built for configuring bundle size thresholds that control whether the GitHub check run passes or fails.
The separation from alerts is intentional: as opposed to Slack alerts, thresholds are active PR gates via GitHub check runs. Mixing them in the same UI conflates two different concerns and makes it harder for teams to reason about what will block their PRs vs. what will send a Slack message.
Bundle Size Thresholds
Each threshold is a standalone rule configured in the Bundles settings tab:
| Field | Description | Required |
|---|---|---|
| Name | Human-readable label (e.g., “Main app install size budget”) | Yes |
| Metric | install_size or download_size |
Yes |
| Deviation percentage | Maximum allowed increase before failing (e.g., 5.0%) | Yes |
| Baseline branch | Branch to compare against (e.g., main) |
Yes |
| Bundle name | Filter to a specific app bundle identifier | No |
Multiple thresholds can be configured per project (e.g., one for install size at 5%, another for download size at 10%, or different thresholds per app in a multi-app project).
Server-Side Evaluation and GitHub Check Run
No changes are needed to the POST /bundles API response or the tuist inspect bundle CLI command. The command behaves exactly as it does today. All threshold logic is server-side and communicated to the developer via a GitHub check run.
Threshold evaluation only happens for bundles uploaded from CI environments. Local uploads are never checked against thresholds — this prevents developers from being blocked during local iteration, where cache invalidations or experimental builds may temporarily inflate bundle sizes.
When tuist inspect bundle uploads a bundle from CI, the server:
- Finds the latest bundle on the configured baseline branch matching the same app bundle identifier.
- Compares the relevant size metric (install or download) of the uploaded bundle against that baseline.
- Creates a GitHub check run (named
tuist/bundle-size) on the commit:successconclusion if no thresholds are exceeded.action_requiredconclusion if a threshold is exceeded, with an “Accept” action button and a link to the bundle page.
The comparison is always against a fixed baseline branch, not a rolling window. This is intentional: you want to know “how does this PR compare to main?” rather than “how does this compare to the last upload on this same branch?”
There is a single tuist/bundle-size check run per project that evaluates all configured threshold rules. If any rule is violated, the check run requires action. If multiple thresholds are exceeded, only the first violation is reported in the check run summary.
Handling Expected Increases
Sometimes a significant bundle size increase is expected and intentional (e.g., adding a new SDK, large asset migration). Since enforcement happens via GitHub check runs rather than the CLI exit code, the override flow happens directly in GitHub:
- When a threshold is exceeded, the server creates a check run with
action_requiredconclusion and an “Accept” action button. - The developer (or reviewer) clicks “Accept” directly in the GitHub PR checks UI — no need to leave GitHub.
- GitHub sends a
check_run.requested_actionwebhook to the Tuist server. - The server updates the check run conclusion to
success— the PR is unblocked immediately, no CI re-run needed.
Accepting applies to all violations on that commit at once — there is no per-threshold granularity. The acceptance is scoped to a specific commit, so future commits on the same branch are still checked against the threshold.
Usage Examples
Basic setup
- Connect the Tuist GitHub App to your repository (required for check runs).
- Navigate to project settings > Bundles in the Tuist dashboard.
- Create a new threshold:
- Name: “Install size budget”
- Metric: Install size
- Deviation: 5%
- Baseline branch:
main
- Add
tuist inspect bundle MyApp.ipato your CI pipeline. - Add
tuist/bundle-sizeas a required status check in your GitHub branch protection rules.
CI pipeline (GitHub Actions)
- name: Check bundle size
run: tuist inspect bundle build/MyApp.ipa
# Always succeeds — enforcement happens via the GitHub status check
Accepting an expected increase
If a threshold is exceeded and the increase is intentional:
- Developer sees the
tuist/bundle-sizecheck run withaction_requiredstatus in the PR. - Clicks the “Accept” button directly in the GitHub checks UI.
- The check run flips to
success— PR is unblocked, no CI re-run needed.
Alternatives Considered
Fail tuist inspect bundle directly (non-zero exit code)
Have the CLI command itself exit non-zero when a threshold is exceeded, similar to tuist inspect dependencies --only implicit. Rejected because:
- Once the CLI exits non-zero, the only way to unblock the PR is to re-run the entire CI job, which rebuilds everything.
- There’s no clean “accept” workflow — the developer would need to either modify the CI pipeline (adding a skip flag, polluting the PR diff) or adjust the threshold.
- GitHub check runs provide a better enforcement model: the server can update the check from
action_requiredtosuccesswithout re-running CI, and the developer can accept directly in the GitHub UI.
Reuse existing alert rules with a fail_command toggle
Add a boolean fail_command field to alert rules with the bundle_size category. When enabled, the server evaluates the alert rule synchronously during bundle upload and violations cause the CLI to fail.
This would avoid introducing a new settings surface, but was rejected because:
- Alerts and CI gates serve different purposes — alerts are passive notifications, thresholds are active blockers. Bundling them together makes it harder to reason about CI behavior.
- Alert rules have fields that don’t apply to thresholds (Slack channel, cooldown period, rolling window) and vice versa. The UI would need conditional field visibility that adds complexity.
- A dedicated Bundles tab provides a clearer home for future bundle-related settings (e.g., absolute size budgets, per-artifact thresholds).
Client-side threshold configuration in Tuist.swift
// NOT proposed
let tuist = Tuist(inspectOptions: .init(
bundleSizeThreshold: .init(installSize: .percentage(5), comparedTo: "main")
))
Rejected because:
- Requires the CLI to fetch the baseline bundle size from the server anyway, duplicating server logic.
- Splitting configuration between manifest and dashboard is confusing.
- The server already stores bundle history; it should own the comparison logic.
Separate tuist inspect bundle --check subcommand
A dedicated subcommand that only performs the threshold check without uploading. Rejected because it would require a second server round-trip and the upload + check flow is the natural CI workflow.
Percentage-only thresholds (no absolute size budgets)
The initial version only supports percentage-based deviation thresholds. Absolute size budgets (e.g., “fail if install size exceeds 100 MB”) could be added later but are out of scope for this RFC.
Implementation References
GitHub Check Runs API
We use the Check Runs API instead of the simpler commit statuses API because check runs support the action_required conclusion with custom action buttons directly in the GitHub UI.
Creating a check run via POST /repos/{owner}/{repo}/check-runs (GitHub docs):
{
"name": "tuist/bundle-size",
"head_sha": "abc123...",
"status": "completed",
"conclusion": "action_required",
"details_url": "https://tuist.dev/team/project/bundles/123",
"output": {
"title": "Bundle size threshold exceeded",
"summary": "Install size increased by 9.68% (threshold: 5.0%)\nPrevious: 45.3 MB (main) → Current: 49.7 MB"
},
"actions": [
{
"label": "Accept",
"description": "Accept this size increase",
"identifier": "accept_bundle_size"
}
]
}
conclusion: action_required: Shows as a failing check that requires developer action. Whenactionsare provided, GitHub renders action buttons directly in the PR checks UI.actions: Up to 3 buttons (max 20 char label, 40 char description, 20 char identifier). When clicked, GitHub sends acheck_run.requested_actionwebhook event to the Tuist server with therequested_action.identifier.details_url: Links to the bundle page in the Tuist dashboard for a detailed breakdown.
Updating a check run via PATCH /repos/{owner}/{repo}/check-runs/{check_run_id} — when the server receives the check_run.requested_action webhook with identifier: "accept_bundle_size", it updates the check run’s conclusion to success.
Permission: Requires the GitHub App installation token with “Checks” write permission. The Tuist GitHub App currently does not have this permission, so we’ll need to bump the app’s permissions. GitHub will prompt existing installations to approve the new permission — this is a standard flow and should be low-friction for users.
Webhook handling: The server needs to handle the check_run webhook event (action: requested_action) in the existing GitHub webhook controller (server/lib/tuist_web/controllers/webhooks/github_controller.ex). This follows the same pattern as the existing issue_comment and installation event handlers.