How I drive a cloud coding agent from my phone through the browser | antigravity-cloud-run
antigravity-cloud-run
How I drive a cloud coding agent from my phone through the browser
tl;dr: I put Google’s Identity-Aware Proxy in front of a Cloud Run service<br>running a coding agent in a web terminal. Now I open a URL on my phone, sign in<br>with my Google account, and I’m talking to the agent.
Source: github.com/ykdojo/antigravity-cloud-run
This builds on my containerized dev environments<br>setup: the agent runs with<br>--dangerously-skip-permissions inside a container on Cloud Run, reachable<br>through a web terminal. The missing piece was using it away from my laptop.
This particular setup was done for Antigravity CLI, but it should work for<br>pretty much any other CLI coding agent.
Two ways to reach a session from a phone, and why I picked IAP
My sessions already join my Tailscale network, so the obvious route was the<br>Tailscale app on the phone. It works, but there’s a catch: tailnet traffic<br>bypasses Cloud Run’s front end entirely, so the autoscaler thinks the service<br>is idle and reclaims the instance after about 15 minutes, mid-use. The only fix<br>is pinning the instance with min-instances=1, which costs money around the<br>clock whether I’m using it or not.
IAP flips that. Identity-Aware Proxy is Google Cloud’s managed sign-in gate:<br>you put it in front of a service, and only the Google accounts you allowlist<br>get through. You open the service’s regular run.app URL, sign in, and you’re<br>at the terminal. The terminal’s<br>WebSocket is a real ingress request, so opening the tab wakes the instance,<br>keeps it alive while you’re connected, and lets it scale back to zero when you<br>close the tab.
If you want the instance to never die, deploy with min-instances=1 and it<br>never scales down to zero.
The stack, layer by layer
IAP decides who gets in. Only the Google accounts you allowlisted can<br>access it.
ttyd turns the browser into a terminal. The agent is a TUI and the<br>browser speaks HTTP/WebSocket; ttyd bridges the two.
tmux keeps the session alive when the connection drops, and you can<br>attach to the same session from multiple devices.
agy does the work.
Setting it up
Enabling IAP on the service itself is three commands:
gcloud run services update SERVICE --region REGION --iap
gcloud run services add-iam-policy-binding SERVICE --region REGION \<br>--member serviceAccount:service-PROJECT_NUMBER@gcp-sa-iap.iam.gserviceaccount.com \<br>--role roles/run.invoker
gcloud beta iap web add-iam-policy-binding --resource-type=cloud-run \<br>--service SERVICE --region REGION \<br>--member user:YOU@gmail.com --role roles/iap.httpsResourceAccessor
If your project lives in a Google Cloud organization, you may be done. Mine<br>doesn’t, so I needed to take a long detour. In case you need to go through the<br>same thing, here is what I went through.
The 502 detour: no organization, no automatic OAuth client
After enabling IAP, every request returned a 502 with this body:
Empty Google Account OAuth client ID(s)/secret(s).
The reason: IAP’s Google-managed OAuth client only authenticates<br>users inside your organization . A personal project has no organization, so<br>there is no client at all, which is why it says “empty”. External users need a custom OAuth<br>client handed to IAP. Four steps, mostly console clicks:
Branding (console, Google Auth Platform → Overview → Get started): app<br>name, support email, audience External, agree to the API user-data policy.
Test users (Audience page): while the consent screen is in Testing,<br>only listed test users can sign in. Add every account you granted the<br>accessor role to. Testing mode also expires sign-ins after about 7 days;<br>publishing the app removes that if the weekly re-sign-in bothers you.
Custom OAuth client (Clients page): type Web application. Add this<br>authorized redirect URI, with your new client’s ID substituted in:<br>https://iap.googleapis.com/v1/oauth/clientIds/CLIENT_ID:handleRedirect.<br>Note that Google now shows the secret only at creation time, so grab it<br>then.
Hand the client to IAP , at the project level so every IAP service in<br>the project inherits it:
# iap_settings.yaml<br>access_settings:<br>oauth_settings:<br>client_id: CLIENT_ID<br>client_secret: CLIENT_SECRET
gcloud iap settings set iap_settings.yaml --project PROJECT
Delete the yaml afterwards. IAP stores the secret as a hash.
The change takes effect in seconds: the 502 is gone, and the service URL<br>redirects to a Google sign-in instead. On the phone: open<br>the service URL, pick your Google account, and the terminal loads.
What IAP breaks
One thing stops working: gcloud run services proxy, which is how my<br>dashboard embeds cloud terminals locally on my laptop. IAP rejects the proxy’s tokens, and<br>on a no-organization project there’s no clean way around it.
So it’s a per-session choice, and I made it a flag: my deploy script takes<br>-i to bring a session up with IAP, and the dashboard shows those sessions<br>as an “open in browser” link instead of an embedded...