Repository navigation
fix(frontend): route automation requests through cloud proxy for cloud backends - #1121
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
🔍 Review in progress… We are performing the review through OpenHands Cloud Automation. |
|
✅ Review complete. This review was performed through OpenHands Cloud Automation. You can log in and view the conversation here. |
all-hands-bot
left a comment
There was a problem hiding this comment.
Code Review: PR #1121
Taste Rating
Acceptable - The fix is correct and well-tested.
Analysis
Files changed: 3 files - proxy.ts, automation-service.api.ts, and their test files.
The Problem: When using a cloud backend, browser requests to automation endpoints were blocked by CORS.
The Fix: Introduces forceProxy flag to route through same-origin proxy, applied via callAutomationCloudProxy helper.
What Works Well:
- Centralized proxy enforcement via helper function
- Comprehensive test coverage for routing and auth preservation
- Good documentation explains the CORS mismatch root cause
- Explicit timeout on health check matches local branch
- Minimal surface area - only 3 files
Minor Observations:
- Omit type does not prevent forceProxy:false being passed (harmless)
- Export visibility change on CloudProxyRequest is correct
- Property-based test assertions are future-proof
Risk Assessment
LOW - Narrowly scoped routing fix with good tests. No breaking changes.
Verdict
Worth merging - Sound fix, thorough tests, clean architecture.
Key Insight
CORS configuration mismatch between main cloud API (loose bearer CORS) and standalone automation service (strict allowlist). Same-origin proxy hop avoids cross-origin entirely.
|
🚀 Released in v1.2.0. |
📸 Snapshot Test ReportWarning Snapshot comparison step crashed (timeout, OOM, or runner error) — diff results below may be incomplete or absent. ✅ No baseline found on main — all 73 snapshots are new and will become the baseline once this PR merges.
🆕 New snapshots (73)These snapshots have no baseline on main and will become the new baseline once this PR merges.
|









































































Why
Summary
With a cloud backend selected (e.g.
https://staging.all-hands.dev), the Automations page (/automations) always renders "Automations Unavailable". DevTools showsGET https://staging.all-hands.dev/api/automation/healthblocked at CORS preflight (provisional headers, 0 response headers).Reproduction
npm run devinagent-server-guihttps://staging.all-hands.dev, TypeCloud, valid API key → select Staging – Personal Workspacehttp://localhost:3001/automationsCurrent: "Automations Unavailable" error state.
Expected: the automations list loads, and all automation actions work.
Root cause
Authorization: Bearer+X-Org-Idheaders (non-simple headers → CORS preflight required)./api/automation/*is served by the standalone automation FastAPI service (automationrepo,openhands/automation/app.py), which uses a plainCORSMiddlewarewhose origin allowlist defaults to the cloud app's own origin only —http://localhost:3001is rejected at preflight.ApiKeyAwareCORSMiddleware(OpenHands/enterprise/server/middleware.py), which answers bearer-token requests withAccess-Control-Allow-Origin: *. The automation service has no such middleware — hence/api/settingssucceeds while/api/automation/healthfails.AutomationService.checkHealth()swallows the network error into{status: "error"}, and the page gates on health → permanent "Automations Unavailable".All 8 cloud branches of
AutomationServiceare affected (list, get, update, delete, dispatch, runs, tarball, health) — health just fails first.Fix
Tunnel cloud automation calls through the bundled agent-server's existing server-side forwarder
POST /api/cloud-proxy(SSRF allowlist already permits*.all-hands.dev), the same proven mechanism used by runtime-sandbox calls. The browser then only makes a same-origin request; the upstream call happens server-side where CORS does not apply.src/api/cloud/proxy.ts: opt-inforceProxyflag onCloudProxyRequestsrc/api/automation-service/automation-service.api.ts: all 8 cloud branches funneled through acallAutomationCloudProxyhelper that injects the flag; 5s fail-fast timeout on the cloud health probe (parity with local)Acceptance criteria
/automationsloads with a cloud backend (staging and prod) selectedPOST /api/cloud-proxyand no direct cross-origin request to<cloud-host>/api/automation/*checkHealthsuccess/error mappingFollow-up (separate ticket,
automationrepo)Consider adopting the main API's bearer-aware CORS behavior (
ApiKeyAwareCORSMiddleware) in the automation service so any external client works without a proxy. Requires a staging/prod deploy; not a substitute for the client-side fix against already-deployed environments.Summary
cloud/proxy.ts— new opt-inforceProxyflag onCloudProxyRequest(now exported). When set, app-host calls go through the bundled agent-server's same-originPOST /api/cloud-proxyhop instead of straight to the cloud host; the upstream call is made server-side where CORS does not apply. Auth +X-Org-Idride in the proxy envelope exactly as the runtime-sandbox calls already do, and the proxy's SSRF allowlist already permits*.all-hands.dev.automation-service.api.ts— all 8 cloud branches (list, get, update, delete, dispatch, runs, tarball, health) are funneled through acallAutomationCloudProxyhelper that injectsforceProxy: true, so a future method can't reintroduce the direct cross-origin call. The cloud health probe also getstimeoutSeconds: 5for parity with the local branch's fail-fast timeout.proxy.test.ts: forceProxy routes viaPOST /api/cloud-proxywith the upstream call as an envelope (and no directaxios.request), and the envelope carriesAuthorization+X-Org-Id.automation-service.test.ts: cloudcheckHealthtunnels through the proxy with the fail-fast timeout and maps proxy rejections to{status: "error"}instead of throwing; the six existing exact-match cloud-routing assertions gained the new flag. Red-check: with the src fix reverted, 9 of these tests fail.Issue Number
Resolves #2385
How to Test
npm run devhttps://staging.all-hands.dev, TypeCloud, valid API key → select Staging – Personal Workspacehttp://localhost:3001/automationsGET .../api/automation/health→ page shows "Automations Unavailable". After: a same-originPOST http://localhost:3001/api/cloud-proxyreturns 200{status:"ok"}and the automations list renders.POST /api/cloud-proxy, with no directstaging.all-hands.devautomation request.Video/Screenshots
app-2159.mov
Type
Notes
*.all-hands.devwould get a 403 from the proxy's SSRF allowlist (OH_CLOUD_PROXY_ALLOWED_HOSTSoverrides it) — the same end state as today's CORS failure, so no regression.automationrepo): adopt the main API's bearer-aware CORS (ApiKeyAwareCORSMiddleware) so external clients work without a proxy; requires a deploy and doesn't help currently-deployed environments, hence the client-side fix here.🐳 Docker images for this PR
• GHCR package: https://github.com/OpenHands/agent-canvas/pkgs/container/agent-canvas
ghcr.io/openhands/agent-canvasghcr.io/openhands/agent-server:1.24.0-pythonopenhands-automation==1.0.0a6082cd40e83be618240d22cf5db341a1116903264Pull (multi-arch manifest)
# Multi-arch manifest — Docker automatically pulls the correct architecture docker pull ghcr.io/openhands/agent-canvas:sha-082cd40Run
All tags pushed for this build
About Multi-Architecture Support
sha-082cd40) is a multi-arch manifest supporting both amd64 and arm64sha-082cd40-amd64) are also available if needed