I drive a cloud coding agent from my phone through the browser

ykev1 pts0 comments

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...

cloud service google agent client phone

Related Articles