Deployment failed for project rac with the following error:
| 1 | Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day"). |
Learn More: https://vercel.com/knewton?upgradeToPro=build-rate-limit
Problem. The shared MacBook Air is overloaded: load average around 125 on 8 cores and 16 GB, two Chromium builds plus about a dozen browser launches at once. The maintainer approved moving compiles and browser launches to their MacBook Pro (18 cores, 48 GB), which is also their daily-driver machine. Threads, git and reviews stay on the Air.
Change. devutils/rac/remote-build.sh runs everything over plain ssh, with every file under ~/rac-remote/ on the Pro:
setup-host: prints the Pro's cores, RAM, disk and tools, and compares both machines' Xcode build and SDK. It refuses on a mismatch, because a tree built with another SDK recompiles from scratch.seed-base <sync worktree> <tag>: one resumable rsync of the 156 base's build/src. It rewrites siso's file state to the Pro path the same way worktree.sh setup does.push <worktree>: committed state only, through a bare repo. It never copies build/. It then runs worktree.sh setup or sync on the Pro.build <worktree> [-- targets]: runs through the Pro's own build queue under nice -n 10 and taskpolicy -c utility, with siso capped at cores minus 4. It streams output and returns the remote exit code.run <worktree>: uses only worktree.sh run (the clone's own profile and Crashpad folder) with --rac-launch-in-background and a timeout. It kills only processes inside the clone. It refuses absolute or .. paths, any Application Support, real profile folder names, single-dash switches, --user-data-dir and --rac-window-screenshot-activate.pull, status, and a --dry-run flag.docs/operations/worktrees.md gets a short "Building on the MacBook Pro" section.Verified.
bash -n and shellcheck -S warning are clean.setup-host against the Pro printed this Mac: Xcode 27.1 (build 27A9275, SDK 27.0) and the Pro: Xcode 26.6 (build 17F113, SDK 26.5), then refused with exit 2. It created nothing on the Pro.~/rac-remote, reuse a real profile, or kill another clone's browser.Not verified. seed-base, push, build and run haven't run against the real Pro. They wait on the maintainer's Xcode choice: install Xcode 27.1 on the Pro, or do a native cold build there.
Patch surface (D17): no change, because this PR touches no patches.
π€ Generated with Claude Code
Note
Medium Risk New remote orchestration runs git force-push, queued compiles, and targeted process teardown on a maintainer machine, though scope and argument validation are heavily constrained and covered by tests.
Overview
Adds devutils/rac/remote-build.sh, a CLI that offloads Chromium compiles and screenshot browser runs to a maintainer MacBook Pro over non-interactive SSH, keeping work confined to ~/rac-remote (bare repos, base/, per-worktree clones in wt/, queue state, logs).
Base setup replaces AirβPro tree sync: build-base <ref> clones the ref on the Pro, bootstraps like a first local build (he presetup, merge, explicit quilt push, worktree.sh sync, he configure), then optionally queues a detached cold build only with --approved. seed-base is removed in favor of this native path. setup-host initializes bare repos and reports both machinesβ Xcode/SDK (mismatch is informational, not a hard stop).
Day-to-day flow: push (committed state only, helium-chromium pin handling), build / run through the existing build-queue.sh with reserved cores and nice/taskpolicy, pull via rsync, plus log / status. Operations refuse unsafe states (dirty trees, queue/busy clones, Xcode/SDK drift vs base/.rac-toolchain copied into clones, symlink escapes, dangerous run args).
Ships a large darwin-only unittest suite (test_remote_build.py) using fake ssh/rsync, and documents the workflow in docs/operations/worktrees.md.
Reviewed by Cursor Bugbot for commit f968abbb692a9431062f7daf491b56ebe60afa90. Bugbot is set up for automated code reviews on this repo. Configure here.
Added 1 commit
Deployment failed for project rac with the following error:
| 1 | Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day"). |
Learn More: https://vercel.com/knewton?upgradeToPro=build-rate-limit
Your OpenCode Zen account is out of credit. Top it up, then re-trigger Pullfrog.
| 1 | provider error: Upstream request failed: Insufficient account funds |
PullfrogΒ Β ο½ Rerun failed job β ο½ View workflow run ο½ via Pullfrog ο½ Using kimi-k3
Important
Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.
888f34cd-7da5-46b6-a417-708ee7ae6804You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Use the checkbox below for a quick retry:
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 @coderabbitai help to get the list of available commands.
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: 718839b6-c21c-4fe1-a72f-cb63c4e931d3)
Devin Review found 5 potential issues.
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 581 | + | *) dest=./$dest ;; | |
| 582 | + | esac | |
| 583 | + | # Pro to Air: the clone is only read. | |
| 584 | + | xrsync "$host:$rdir/wt/$name/$rel" "$dest" | |
π¨ Pull paths can read beyond the clone
When an allowed pull path traverses a symlink inside the clone, rsync reads its external target. The lexical .. check cannot enforce the clone-only boundary.
Was this helpful? React with π or π to provide feedback.
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 531 | + | launcher=$! | |
| 532 | + | end=$((SECONDS + timeout)) | |
| 533 | + | last=-1 | |
| 534 | + | while [ "$SECONDS" -lt "$end" ]; do | |
π‘ Queue wait consumes browser timeout
When run slots stay busy for the timeout, cmd_run kills the queued launcher before the browser starts. Screenshot runs fail without ever launching a browser.
The build queue waits indefinitely until a run slot is free. Here SECONDS starts counting while that wait is still in progress, and the caller terminates the queue at the deadline. A screenshot request then reports failure even though no browser was started.
Example: With both Pro run slots held for 95 seconds, run feature -- --rac-window-screenshot=x.png times out at 90 seconds and produces no screenshot, even if the slots free at 95 seconds.
Recommended fix: Separate queue-acquisition time from the browser runtime limit, or start the runtime deadline when the queue confirms the run slot and launches the browser.
Was this helpful? React with π or π to provide feedback.
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 497 | + | case "$arg" in | |
| 498 | + | --rac-window-screenshot=*) | |
| 499 | + | shot="$clone/build/shots/${arg#--rac-window-screenshot=}" | |
| 500 | + | arg="--rac-window-screenshot=$shot" ;; | |
π₯ Relative screenshot paths can escape clone via symlinks
When build/shots contains a symlink to another directory, run writes screenshots through that symlink. Its lexical path check cannot keep screenshots inside the clone or protect the maintainer's files.
Was this helpful? React with π or π to provide feedback.
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 89 | + | only_patch_state() { | |
| 90 | + | [ -z "$(awk '/^\?\? "?patches\/rac\// { print; next } | |
| 91 | + | /^\?\? / || /^.?D "?patches\/series"?$/ { next } | |
| 92 | + | { print }')" ] | |
π‘ Untracked source files vanish from remote builds
When a worktree contains a new untracked source file, only_patch_state lets push proceed without sending it. The remote build then runs the committed tree instead of the local worktree's code.
The local cmd_push checks this filter and sends only the current branch commit. This filter treats every ?? entry as expected merged-patch state, including new scripts and assets outside patches/. Git does not include those files in the push, so the remote clone differs from the worktree the developer requested.
Example: Add resources/new-icon.svg without staging it, then run push followed by build. Push succeeds; the remote checkout has no icon file even though it exists locally.
Recommended fix: Permit only known generated merged-patch files, and reject other untracked paths. Use untracked-file expansion when checking directories so an untracked patches/ directory cannot hide new rac patches.
Was this helpful? React with π or π to provide feedback.
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 304 | + | DEVELOPER_DIR=$xcode "$siso" fs export -C "$src/out/Default" \ | |
| 305 | + | | python3 -c 'import sys; o, n = sys.argv[1:]; sys.stdout.write(sys.stdin.read().replace(o, n))' \ | |
| 306 | + | "$1/" "$PWD/$src/" \ | |
| 307 | + | | DEVELOPER_DIR=$xcode "$siso" fs import -C "$src/out/Default" | |
π΄ Failed export corrupts seeded build state
If siso fs export fails during seed-base, the pipeline can still succeed after importing empty state. The base loses its incremental build state, so subsequent clones can trigger full Chromium rebuilds.
The remote seed-base script uses set -e, but a shell pipeline returns only its last command's status. If siso fs export or the Python rewrite fails, siso fs import can succeed on its remaining input. The caller then proceeds to GN and reports a seeded base despite losing the exported state. cmd_setup uses pipefail and explicitly rejects failed state transfers.
Example: If export exits 1 without producing data while import exits 0, seed-base finishes and reports success; the first feature clone inherits missing or invalid Siso state instead of the Air's completed build state.
Recommended fix: Enable pipefail in the remote seed script and propagate any export, rewrite, or import error before reporting success; also validate the imported state before cloning from it.
Was this helpful? React with π or π to provide feedback.
[Medium impact] The PR extends the maintainer's remote build and browser workflow to feature worktrees on the Pro.
Do not merge until remote screenshot paths are contained and pushes and runs are coordinated safely.
| 1 | ### Issue 1 |
| 2 | devutils/rac/remote-build.sh:440-449 |
| 3 | `check_run_arg` accepts `--rac-window-screenshot-requests=requests.txt`. Combined with `--rac-window-screenshot=x.png`, this makes the browser activate itself and read output paths from `requests.txt` without checking them. A file containing an absolute path or `../` can overwrite files outside the clone. |
| 4 | |
| 5 | Refuse this switch for remote runs unless both its foreground behavior and its requested output paths are made safe. |
| 6 | |
| 7 | **How this was verified:** The accepted relative request-file switch reaches code that activates the app and passes the file's contents to `Capture` and `WriteBytes` without a path check. |
| 8 | |
| 9 | ### Issue 2 |
| 10 | devutils/rac/remote-build.sh:363-367 |
| 11 | Pushing another commit while the clone is compiling resets the checkout and runs `sync` without entering the queue or taking a shared lock. `sync` pops and reapplies source patches, so it can change files underneath the compiler and leave a failed or mixed build. |
| 12 | |
| 13 | Make updates and builds share a per-clone lock, held across the reset and sync. |
| 14 | |
| 15 | ### Issue 3 |
| 16 | devutils/rac/remote-build.sh:364-367 |
| 17 | When a pushed commit changes the pinned `helium-chromium` commit, `push` transfers only the parent repository. `git reset --hard` changes the pin but leaves the submodule checkout at its old commit. `merge` then runs the old submodule's script and uses its old patches; `worktree.sh sync` does not update it either. |
| 18 | |
| 19 | Transfer and check out the pinned submodule commit before merging patches, or explicitly refuse unsupported pin changes. |
| 20 | |
| 21 | ### Issue 4 |
| 22 | devutils/rac/remote-build.sh:514-519 |
| 23 | `stop_clone` kills every browser from the clone, not just the browser started by this call. The queue allows overlapping runs of the same worktree, and those runs share a browser profile. Whichever call finishes first can therefore kill the other's browser and lose its output. |
| 24 | |
| 25 | Hold a per-clone run lock before touching screenshots or launching, or limit cleanup to processes owned by this call. |
| 26 | |
| 27 | ### Issue 5 |
| 28 | devutils/rac/remote-build.sh:529-534 |
| 29 | The timeout starts before the queue grants a slot. If both run slots stay busy for 90 seconds, the default call kills its waiting queue process without ever launching a browser. Without a screenshot argument, `status=0` then reports success. A slot granted late also leaves the browser only the remaining time. |
| 30 | |
| 31 | Start the browser timeout inside the queued command after it gets a slot. Return an error if a separate queue-wait limit expires. |
| 32 | |
| 33 | --- |
| 34 | |
| 35 | For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
Adds remote-build.sh to seed a remote base, push committed worktrees, queue builds and browser runs, and retrieve files.
Review used source inspection only; no runtime verification was performed.
| 1 | %%{init: {'theme': 'neutral'}}%% |
| 2 | flowchart TD |
| 3 | Air[Air: remote-build.sh] -->|ssh and git push| Repos[Pro: bare repositories] |
| 4 | Air -->|rsync build/src| Base[Pro: seeded base] |
| 5 | Repos --> Push[Create or update worktree] |
| 6 | Base --> Push |
| 7 | Push --> Clone[Remote clone] |
| 8 | Clone --> Queue[Remote build queue] |
| 9 | Queue --> Build[Compile] |
| 10 | Queue --> Run[Launch browser] |
| 11 | Run --> Shots[Screenshots] |
| 12 | Shots -->|rsync pull| Air |
Reviews (1) Β· Last reviewed commit: "feat(devutils): add remote-build.sh to b..." Β· Reviewed by Greptile
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 516 | + | for i in 1 2 3 4 5 6 7 8 9 10; do | |
| 517 | + | pids=$(clone_pids) | |
| 518 | + | [ -n "$pids" ] || return 0 | |
| 519 | + | kill $pids 2>/dev/null || true | |
P1 Runs close each other's browser
stop_clone kills every browser from the clone, not just the browser started by this call. The queue allows overlapping runs of the same worktree, and those runs share a browser profile. Whichever call finishes first can therefore kill the other's browser and lose its output.
Hold a per-clone run lock before touching screenshots or launching, or limit cleanup to processes owned by this call.
| 1 | This is a comment left during a code review. |
| 2 | Path: devutils/rac/remote-build.sh |
| 3 | Line: 514-519 |
| 4 | |
| 5 | Comment: |
| 6 | **Runs close each other's browser** |
| 7 | |
| 8 | `stop_clone` kills every browser from the clone, not just the browser started by this call. The queue allows overlapping runs of the same worktree, and those runs share a browser profile. Whichever call finishes first can therefore kill the other's browser and lose its output. |
| 9 | |
| 10 | Hold a per-clone run lock before touching screenshots or launching, or limit cleanup to processes owned by this call. |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 531 | + | launcher=$! | |
| 532 | + | end=$((SECONDS + timeout)) | |
| 533 | + | last=-1 | |
| 534 | + | while [ "$SECONDS" -lt "$end" ]; do | |
P1 Waiting runs can succeed silently
The timeout starts before the queue grants a slot. If both run slots stay busy for 90 seconds, the default call kills its waiting queue process without ever launching a browser. Without a screenshot argument, status=0 then reports success. A slot granted late also leaves the browser only the remaining time.
Start the browser timeout inside the queued command after it gets a slot. Return an error if a separate queue-wait limit expires.
| 1 | This is a comment left during a code review. |
| 2 | Path: devutils/rac/remote-build.sh |
| 3 | Line: 529-534 |
| 4 | |
| 5 | Comment: |
| 6 | **Waiting runs can succeed silently** |
| 7 | |
| 8 | The timeout starts before the queue grants a slot. If both run slots stay busy for 90 seconds, the default call kills its waiting queue process without ever launching a browser. Without a screenshot argument, `status=0` then reports success. A slot granted late also leaves the browser only the remaining time. |
| 9 | |
| 10 | Start the browser timeout inside the queued command after it gets a slot. Return an error if a separate queue-wait limit expires. |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 364 | + | git reset -q --hard "$3" | |
| 365 | + | devutils/update_patches.sh merge | |
| 366 | + | if [ -d build/src ]; then | |
| 367 | + | devutils/rac/worktree.sh sync | |
P1 Push changes an active build
Pushing another commit while the clone is compiling resets the checkout and runs sync without entering the queue or taking a shared lock. sync pops and reapplies source patches, so it can change files underneath the compiler and leave a failed or mixed build.
Make updates and builds share a per-clone lock, held across the reset and sync.
| 1 | This is a comment left during a code review. |
| 2 | Path: devutils/rac/remote-build.sh |
| 3 | Line: 363-367 |
| 4 | |
| 5 | Comment: |
| 6 | **Push changes an active build** |
| 7 | |
| 8 | Pushing another commit while the clone is compiling resets the checkout and runs `sync` without entering the queue or taking a shared lock. `sync` pops and reapplies source patches, so it can change files underneath the compiler and leave a failed or mixed build. |
| 9 | |
| 10 | Make updates and builds share a per-clone lock, held across the reset and sync. |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 446 | + | *=?*) ;; | |
| 447 | + | *) refuse "give the screenshot file as --rac-window-screenshot=<name>.png" ;; | |
| 448 | + | esac ;; | |
| 449 | + | esac ;; | |
P1 security Requests bypass screenshot safety checks
check_run_arg accepts --rac-window-screenshot-requests=requests.txt. Combined with --rac-window-screenshot=x.png, this makes the browser activate itself and read output paths from requests.txt without checking them. A file containing an absolute path or ../ can overwrite files outside the clone.
Refuse this switch for remote runs unless both its foreground behavior and its requested output paths are made safe.
How this was verified: The accepted relative request-file switch reaches code that activates the app and passes the file's contents to Capture and WriteBytes without a path check.
| 1 | This is a comment left during a code review. |
| 2 | Path: devutils/rac/remote-build.sh |
| 3 | Line: 440-449 |
| 4 | |
| 5 | Comment: |
| 6 | **Requests bypass screenshot safety checks** |
| 7 | |
| 8 | `check_run_arg` accepts `--rac-window-screenshot-requests=requests.txt`. Combined with `--rac-window-screenshot=x.png`, this makes the browser activate itself and read output paths from `requests.txt` without checking them. A file containing an absolute path or `../` can overwrite files outside the clone. |
| 9 | |
| 10 | Refuse this switch for remote runs unless both its foreground behavior and its requested output paths are made safe. |
| 11 | |
| 12 | **How this was verified:** The accepted relative request-file switch reaches code that activates the app and passes the file's contents to `Capture` and `WriteBytes` without a path check. |
| 13 | |
| 14 | --- |
| 15 | |
| 16 | For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
| β¦ | β¦ | @@ -0,0 +1,633 @@ | |
| 364 | + | git reset -q --hard "$3" | |
| 365 | + | devutils/update_patches.sh merge | |
| 366 | + | if [ -d build/src ]; then | |
| 367 | + | devutils/rac/worktree.sh sync | |
P1 Helium updates stay behind
When a pushed commit changes the pinned helium-chromium commit, push transfers only the parent repository. git reset --hard changes the pin but leaves the submodule checkout at its old commit. merge then runs the old submodule's script and uses its old patches; worktree.sh sync does not update it either.
Transfer and check out the pinned submodule commit before merging patches, or explicitly refuse unsupported pin changes.
| 1 | This is a comment left during a code review. |
| 2 | Path: devutils/rac/remote-build.sh |
| 3 | Line: 364-367 |
| 4 | |
| 5 | Comment: |
| 6 | **Helium updates stay behind** |
| 7 | |
| 8 | When a pushed commit changes the pinned `helium-chromium` commit, `push` transfers only the parent repository. `git reset --hard` changes the pin but leaves the submodule checkout at its old commit. `merge` then runs the old submodule's script and uses its old patches; `worktree.sh sync` does not update it either. |
| 9 | |
| 10 | Transfer and check out the pinned submodule commit before merging patches, or explicitly refuse unsupported pin changes. |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
Added 1 commit
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: 33120521-2101-4921-82fd-e69a8636f730)
Your OpenCode Zen account is out of credit. Top it up, then re-trigger Pullfrog.
| 1 | provider error: Upstream request failed: Insufficient account funds |
PullfrogΒ Β ο½ Rerun failed job β ο½ View workflow run ο½ via Pullfrog ο½ Using kimi-k3
Added 1 commit
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: 86bee605-844d-4a82-ac1c-33966fb37890)
Your OpenCode Zen account is out of credit. Top it up, then re-trigger Pullfrog.
| 1 | provider error: Upstream request failed: Insufficient account funds |
PullfrogΒ Β ο½ Rerun failed job β ο½ View workflow run ο½ via Pullfrog ο½ Using kimi-k3
Added 1 commit
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: f500ad57-19f1-4471-a1b7-5099b3b4bfe1)
Your OpenCode Zen account is out of credit. Top it up, then re-trigger Pullfrog.
| 1 | provider error: Upstream request failed: Insufficient account funds |
PullfrogΒ Β ο½ Rerun failed job β ο½ View workflow run ο½ via Pullfrog ο½ Using kimi-k3
Added 1 commit
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: 0ac8b748-caf1-4f82-876c-697059dd5701)
Your OpenCode Zen account is out of credit. Top it up, then re-trigger Pullfrog.
| 1 | provider error: Upstream request failed: Insufficient account funds |
PullfrogΒ Β ο½ Rerun failed job β ο½ View workflow run ο½ via Pullfrog ο½ Using kimi-k3