Skip to content

fix(frontend): stop calling /api/auth/workspace-session on cloud backends#763

Merged
hieptl merged 1 commit into
mainfrom
hieptl/app-1927
May 26, 2026
Merged

fix(frontend): stop calling /api/auth/workspace-session on cloud backends#763
hieptl merged 1 commit into
mainfrom
hieptl/app-1927

Conversation

@hieptl
Copy link
Copy Markdown
Contributor

@hieptl hieptl commented May 26, 2026

  • A human has tested these changes.

Why

Description

Summary

When a user opens the Files tab on a conversation running against a cloud backend (e.g. Production - Personal Workspace), the frontend issues a POST /api/auth/workspace-session against the conversation's runtime sandbox (routed server-side through callCloudProxy). The call is structurally useless on cloud — it produces dead network traffic on every cloud conversation that opens the Files tab, and the file-content fetch that depends on its result is silently broken on cloud.

Steps to reproduce

  1. npm run dev in agent-canvas.
  2. Open the <BackendSelector />, click Add Backend, and configure:
    • Host Name: Production
    • Host: https://app.all-hands.dev
    • Type: Cloud
    • API Key: Personal-Workspace API key from OpenHands production
  3. Select Production - Personal Workspace, create a new conversation.
  4. Send: Write a hello world script in bash.
  5. After the agent finishes, open the Files tab.
  6. Inspect DevTools Network panel.

Current behavior

The endpoint http://<id>.staging-runtime.all-hands.dev/api/auth/workspace-session is called (visible in the /api/cloud-proxy envelope and in upstream logs).

Expected behavior

/api/auth/workspace-session should not be called on cloud. The Files tab should still render file lists and file content.

Root cause

useWorkspaceSession exists to mint an oh_workspace_session_key cookie so the browser can attach it to subsequent same-origin <iframe src> / <img src> / fetch(credentials: "include") calls against the static workspace fileserver. That flow only works for local backends.

On cloud:

  1. The POST is made server-side through callCloudProxy, so any upstream Set-Cookie lands at the local agent-server and never reaches the browser jar.
  2. Even if it did, the static fileserver is on *.staging-runtime.all-hands.dev and the GUI runs on localhostfetch(staticUrl, { credentials: "include" }) cannot carry the cookie cross-origin.

So useWorkspaceSession fires a POST that does nothing, and useWorkspaceFileContent builds a staticUrl whose cookie-authenticated fetch can never succeed.

Fix

Mirror the pattern used in the original OpenHands frontend: do not mint a workspace session on cloud. Fetch file bytes through callCloudProxy (which already attaches X-Session-API-Key server-side), and render binary kinds as base64 data: URIs.

Acceptance criteria

  • Opening the Files tab on a cloud conversation produces zero /api/auth/workspace-session calls (browser-direct or wrapped in /api/cloud-proxy).
  • The file list still renders on cloud.
  • Text/HTML/Markdown/image/PDF previews still render on cloud (via data: URIs).
  • Local conversations still mint the workspace-session cookie exactly as before.
  • "Open in new window" still works on both backends.

Summary

  • Disable useWorkspaceSession on cloud backends — the cookie it mints is unusable cross-origin and the POST was pure dead traffic.
  • Route cloud file content through AgentServerRuntimeService.downloadFile (already proxied with X-Session-API-Key). Binary kinds become base64 data: URIs.
  • WorkspaceFileContent shape unchanged — file-content-viewer.tsx and files-tab.tsx don't need to branch on backend kind.

Why

POST /api/auth/workspace-session exchanges X-Session-API-Key for an oh_workspace_session_key cookie scoped to /api/conversations. The browser then auto-attaches that cookie to same-origin <iframe src> / <img src> / fetch(credentials: "include") against the static workspace fileserver.

That flow works on local, where the GUI and the agent server share an origin. On cloud it breaks at two points:

  1. The POST is dispatched server-side through callCloudProxy, so the upstream Set-Cookie is received by the local agent-server, not the browser.
  2. Even if the cookie made it back, the static fileserver is on *.staging-runtime.all-hands.dev and the GUI on localhost — a cross-origin fetch(credentials: "include") won't attach a cookie set on a different origin.

So the POST was dead traffic and useWorkspaceFileContent's follow-up fetch was silently broken on every cloud conversation that opened the Files tab.

The pattern used by the original OpenHands frontend (/Users/hieple/Documents/openhands/OpenHands/frontend) is to fetch file bytes via authenticated HTTP using X-Session-API-Key and render images as base64 data: URIs. This PR adopts that pattern on cloud.

Changes

src/hooks/query/use-workspace-session.ts

  • Gate enabled on kind === "local". The hook returns { data: null, ... } on cloud — no POST fires.
  • Removed the now-dead cloud branch (the one that built upstream calls via callCloudProxy with hostOverride).
  • Removed unused callCloudProxy / buildHttpBaseUrl imports.
  • Updated the docstring to call out the local-only contract.

src/hooks/query/use-workspace-file-content.ts

  • Added a cloud branch in queryFn that fetches bytes via AgentServerRuntimeService.downloadFile(conversationUrl, sessionApiKey, relativePath) (already routes through callCloudProxy and supplies X-Session-API-Key).
  • For text on cloud: NUL-byte sniff → UTF-8 decode (same heuristic as local), plus staticUrl set to a data:${mimeType};charset=utf-8;base64,... URI so the "open in new window" link in files-tab.tsx still works.
  • For image/PDF/binary on cloud: staticUrl set to a data:${mimeType};base64,... URI.
  • enabled no longer requires baseUrl on cloud (isCloud || !!baseUrl).
  • Query key keys off "cloud" instead of baseUrl on cloud, so cache identity is correct across backend switches.
  • Added a chunked arrayBufferToBase64 helper that avoids the String.fromCharCode(...arr) call-stack limit on larger files.

src/components/features/files-tab/file-content-viewer.tsx, src/routes/files-tab.tsx — unchanged. data: URIs render in <img src> / <iframe src> / <a href> like any other URL.

Tests

Following the project's testing rules (mock underlying services, not the hook; one assertion per behavior; extend existing files):

__tests__/hooks/query/use-workspace-session.test.tsx

  • Replaced the two old cloud tests (which asserted the now-removed callCloudProxy routing) with one test verifying cloud does not fire any request and data stays null.

__tests__/hooks/query/use-workspace-file-content.test.tsx

  • Added a cloud backend describe block.
  • Mocked getActiveBackend (set to cloud) and AgentServerRuntimeService.downloadFile — the underlying dependencies, not the hook itself.
  • Test 1: cloud + text file → downloadFile is called with the expected args, no browser fetch call, returned text is decoded, staticUrl is a data:text/markdown;charset=utf-8;base64,... URI.
  • Test 2: cloud + image file → downloadFile is called, kind: "image", staticUrl is a data:image/png;base64,... URI, no browser fetch call.
Test Files  3 passed (3)
Tests       34 passed (34)

Issue Number

Resolves #762

How to Test

  1. npm run dev in agent-canvas.
  2. Open the <BackendSelector />, click Add Backend, and configure:
    • Host Name: Production
    • Host: https://app.all-hands.dev
    • Type: Cloud
    • API Key: Personal-Workspace API key from OpenHands production
  3. Select Production - Personal Workspace, create a new conversation.
  4. Send: Write a hello world script in bash.
  5. After the agent finishes, open the Files tab.
  6. Inspect DevTools Network panel.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

🐳 Docker images for this PR

GHCR package: https://github.com/OpenHands/agent-canvas/pkgs/container/agent-canvas

Component Value
Image ghcr.io/openhands/agent-canvas
Architectures amd64, arm64
Agent Server ghcr.io/openhands/agent-server:1.23.0-python
Automation openhands-automation==1.0.0a3
Commit 50e005bde95e0e1b4d0f3a2db39f50d549c0edcd

Pull (multi-arch manifest)

# Multi-arch manifest — Docker automatically pulls the correct architecture
docker pull ghcr.io/openhands/agent-canvas:sha-50e005b

Run

docker run -it --rm \
  -p 8000:8000 \
  ghcr.io/openhands/agent-canvas:sha-50e005b

All tags pushed for this build

ghcr.io/openhands/agent-canvas:sha-50e005b-amd64
ghcr.io/openhands/agent-canvas:hieptl-app-1927-amd64
ghcr.io/openhands/agent-canvas:pr-763-amd64
ghcr.io/openhands/agent-canvas:sha-50e005b-arm64
ghcr.io/openhands/agent-canvas:hieptl-app-1927-arm64
ghcr.io/openhands/agent-canvas:pr-763-arm64
ghcr.io/openhands/agent-canvas:sha-50e005b
ghcr.io/openhands/agent-canvas:hieptl-app-1927
ghcr.io/openhands/agent-canvas:pr-763

About Multi-Architecture Support

  • Each tag (e.g., sha-50e005b) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., sha-50e005b-amd64) are also available if needed

@hieptl hieptl self-assigned this May 26, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-canvas Ready Ready Preview, Comment May 26, 2026 8:36am

Request Review

Copy link
Copy Markdown
Contributor

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Good taste - Clean bug fix that eliminates dead network traffic and properly implements cloud file viewing.

KEY INSIGHT:
The PR correctly identifies that the workspace-session cookie flow is architecturally incompatible with cloud (cross-origin + server-side hop), and solves it by routing file bytes through the existing cloud proxy infrastructure with base64 data URIs.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟢 LOW

This is a straightforward bug fix that:

  • Eliminates useless network calls on cloud (the cookie never reached the browser and wouldn't work cross-origin)
  • Fixes broken file viewing by using the correct cloud proxy pattern with X-Session-API-Key
  • Mirrors the proven approach from the original OpenHands frontend
  • Leaves local behavior completely unchanged (still uses workspace-session cookie)
  • Has proper test coverage with correctly mocked dependencies

The implementation correctly uses AgentServerRuntimeService.downloadFile(), which internally routes through FileClient (from @openhands/typescript-client) for local and callCloudProxy for cloud - following the repository's API access conventions. The arrayBufferToBase64 chunking prevents call-stack issues on larger files.

Minor observation: Data URIs have generous size limits in modern browsers (~100MB+), but very large files could theoretically hit browser limits. This is acceptable since (a) the old behavior was completely broken, and (b) this mirrors the original frontend's pattern.


Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/agent-canvas/actions/runs/26441623781

@github-actions
Copy link
Copy Markdown
Contributor

📸 Snapshot Test Report

✅ All snapshots match the main branch baselines.

Category Count
🔴 Changed 0
🆕 New 0
✅ Unchanged 73
Total 73
✅ Unchanged snapshots (73)

archived-conversation

  • conversation-panel-with-archived-badges
  • conversation-view-archived
  • conversation-view-sandbox-error

automations

  • automations-delete-modal
  • automations-list-active-inactive
  • automations-no-automations
  • automations-search-no-results

backends-extended

  • backend-add-blank-disabled
  • backend-add-cloud-advanced-open
  • backend-add-cloud-no-key-disabled
  • backend-add-cloud-with-key-enabled
  • backend-add-form-partially-filled
  • backend-add-invalid-url-disabled
  • backend-add-local-ready
  • backend-add-name-only-disabled
  • backend-add-two-column-layout
  • backend-add-whitespace-host-disabled
  • backend-after-switch
  • backend-cancel-nothing-saved
  • backend-dropdown-two-backends
  • backend-edit-prefilled
  • backend-manage-after-removal
  • backend-manage-two-listed
  • backend-remove-cancelled
  • backend-remove-confirmation
  • backend-switch-overlay

backends

  • backend-add-modal
  • backend-manage-modal
  • backend-selector-open

changes-tab

  • changes-deleted-file
  • changes-diff-viewer
  • changes-empty

collapsible-thinking

  • reasoning-content-collapsed
  • reasoning-content-expanded
  • think-action-collapsed
  • think-action-expanded

mcp-page

  • mcp-custom-server-1-editor-open
  • mcp-custom-server-2-url-filled
  • mcp-custom-server-3-all-filled
  • mcp-custom-server-4-installed
  • mcp-custom-server-editor
  • mcp-empty-installed
  • mcp-search-filtered
  • mcp-slack-install-1-marketplace
  • mcp-slack-install-2-modal
  • mcp-slack-install-3-filled
  • mcp-slack-install-4-installed

onboarding

  • onboarding-step-0-choose-agent
  • onboarding-step-1-check-backend
  • onboarding-step-2-setup-llm
  • onboarding-step-3-say-hello

projects-workspace-browser

  • projects-workspace-browser

settings-page

  • add-backend-modal
  • analytics-consent-modal
  • home-screen
  • settings-app-page
  • settings-page

settings-secrets

  • secrets-add-form-filled
  • secrets-add-form
  • secrets-after-save
  • secrets-delete-confirm
  • secrets-list

settings-verification

  • condenser-settings
  • verification-settings-off
  • verification-settings-on

sidebar

  • sidebar-collapsed
  • sidebar-conversation-panel
  • sidebar-filter-menu

skills-page

  • skills-empty
  • skills-loaded
  • skills-no-match
  • skills-search-filtered
  • skills-type-filter

Generated by the Snapshot Tests workflow. This comment was created by an AI agent (OpenHands) on behalf of the repo maintainers.

@hieptl hieptl merged commit c46f7f2 into main May 26, 2026
19 checks passed
@hieptl hieptl deleted the hieptl/app-1927 branch May 26, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/api/auth/workspace-session is fired on cloud conversations and serves no purpose

2 participants