Engel’s notebook

Backends, Cloud and sharing

F25 · 33 mapped behaviors in the Self-Healing Codebase Project. Follow the prerequisites, actions and expected results; expand the original CLI recipe to replay it.

778 behavior IDs · 28 families · 844 named recipes & support steps
16 October 2026 · PR #18334 at 9a3c3a69
Canvas 1.27.1 · Agent Server 1.55.0 · automation 1.20.0

This is the map at the revision above, including its open bug notes. It does not incorporate later changes on main or claim a fresh verification run. Setup & reading guide · Download the scenario inventory

All scenarios & marker key · Open a script to read Given / Do / Expect. Expand the exact recipe for replayable commands.

F25 Backends, Cloud and sharing

33 behaviors · 37 scripts & contracts · Maintained source

Entry points & prerequisites

What this family covers

One Canvas can drive several agent servers. The backend selector at the bottom of the sidebar lists every registered backend with a live status dot and switches between them; its footer opens Add Backend (OpenHands Cloud login through an OAuth device flow, or a local/remote agent server by host and API key) and Manage Backends (select, edit, remove, and Cloud "Log back in"). When the active backend is unreachable the whole app is replaced by Manage backends in recovery mode. Cloud backends add organization rows, a Cloud settings link and public sharing of conversations (read-only at /shared/conversations/:id); /oauth/device/verify is the device-approval page. When the page is served with the DigitalOcean Managed Agents bridge (the desktop app's preload, or the web server's own bridge, which the launcher's ingress mounts by default behind the session key), Add Backend gains a Providers tab whose DigitalOcean entry signs in with a personal access token and turns a Managed Agents session into a backend. Everything except the Cloud and DigitalOcean accounts themselves can be driven locally with a second control-openhands stack as the extra backend.

How a user gets here

  • Sidebar, bottom: the backend selector (backend-selector; click or hover) → options, Add Backend (add-backend-menu-item), Manage Backends (manage-backends-menu-item).
  • Collapsed rail: hover the backend icon (collapsed-backend-selector-link) → the same footer items.
  • Phone: hamburger (sidebar-mobile-menu-toggle) → drawer → backend selector.
  • Manage backends → Add Backend (manage-backends-add) opens the same Add form.
  • Direct URLs: any in-app URL with ?backend=<id>; /oauth/device/verify[?user_code=CODE]; /shared/conversations/<id>.
  • Conversation header name → "..." (conversation-name >> ellipsis-button) for Public Share (Cloud only).
  • Reloading with an unreachable active backend opens the recovery gate.
  • Onboarding's backend step and the public-mode API-key screen also add backends; they belong to F01. The ChatGPT subscription card is F10 (F10.subscription) and the provider balance card is F27 (F27.usage-provider-balance).

Given

Preconditions:

  • Baseline state (launched, doctored, onboard --skip done) and control-openhands llm preset deepseek on this run.
  • One conversation on this run: control-openhands conversation start --prompt "Reply with exactly: QA_F25_PONG" --wait --timeout 180; note its <id>. The conversation only has to exist: F25 never reads the reply, so a run that ends in error (for example a key without balance, saved with llm preset deepseek --no-validate) is enough.
  • A second stack to add as a backend, started without a browser and never driven directly: export OH_VERIFY_RUN_2=$(OH_VERIFY_RUN= control-openhands launch --new --build never --no-browser --print-run), then control-openhands status --run "$OH_VERIFY_RUN_2"; its baseUrl is <second-url> below and its key is $OH_VERIFY_RUN_2/private/session-key. It has no conversations.
  • No backend named QA_Second or QA_Renamed exists (the selector lists only Local).
  • Bullets run in order: Add creates QA_Second, Edit renames it to QA_Renamed, Remove deletes it.
  • The Managed Agents rows need the bridge, which every control-openhands launch run has: control-openhands browser eval "!!window.marsBridge" is true and curl -s <baseUrl>/mars/health answers {"ok":true,"authRequired":true}. F25.digitalocean-agents and F25.digitalocean-backend-removal stay blocked without a DigitalOcean account.
  • Cloud account: where this machine's proxy injects OpenHands Cloud credentials (curl -s -o /dev/null -w '%{http_code}' https://app.all-hands.dev/api/organizations is 200 without a token), the Cloud backend bullet below can approve the device flow in its popup; otherwise it is blocked. Blocked here even with such an account: F25.cloud-log-back-in (needs a logged-out Cloud row), org switching in F25.cloud-org-rows (needs a second organization), the archived and error states of F25.cloud-sandbox-states, F25.cloud-org-suspended (also needs a suspended OpenHands Enterprise organization or membership), F25.share-publicly (toggle), and F25.locked-cloud, which also needs a launcher flag (see Gotchas).
Open the selectorF25.selector-dropdownKnown failure

Scenario F25--open-the-selector · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Backend selector”.
  2. Do
    Press Escape.
  3. Do
    Hover over “Search commands”.
  4. Do
    Hover over “Backend selector”.

Expect

clicking (or hovering) the selector opens a listbox of backends, each option named <status> <name> with a status dot, and a footer with Add Backend and Manage Backends.

Linked known issues

Expected: Escape never clears the backend selector. Known failure (reproduced 2026-10-09, 2026-10-12, 2026-10-13 and 2026-10-14, #18233): Escape on the focused backend selector while its menu is closed (a second Escape after the one that closes the menu, and sometimes the Escape that closes Manage Backends opened from it) clears the selection: the closed selector shows the active backend's name as an italic placeholder (browser value 'testid=backend-selector >> role=combobox' is "") and its status dot is gone, so browser wait 'testid=backend-selector >> role=status[name="Disconnected"]' times out although the backend is down; a reload restores it. Reload before a closed-selector status check, as the Edit bullet does. Repro: browser click 'testid=backend-selector', browser press Escape twice (#18233). On 2026-10-12 bug proof verified fix PR #18238 at 77de0ad24 against main f07aebdf5 (two xfail, two xpass, desktop). control-openhands bug run F25.selector-dropdown replays it (desktop, after a reload with the mouse off the selector: a reload under a mouse left on it opens its menu and empties its field, which would look like the bug) and reports xfail while the bug lasts: sh known-bug F25.selector-dropdown escape-closed control-openhands browser viewport desktop control-openhands browser goto / control-openhands browser hover 'testid=root-layout' control-openhands browser reload control-openhands browser wait 'testid=backend-selector >> role=status[name="Connected"]' --timeout 15000 control-openhands assert --field value --equals Local -- browser value 'testid=backend-selector >> role=combobox' control-openhands browser click 'testid=backend-selector' control-openhands browser press Escape control-openhands browser press Escape control-openhands browser screenshot 'testid=backend-selector' --feature F25.selector-dropdown --name escape-closed control-openhands assert --field value --equals Local --bug-shows '' -- browser value 'testid=backend-selector >> role=combobox' # bug

Exact CLI recipe & bug checks

From /conversations run control-openhands browser click 'testid=backend-selector' and control-openhands browser snapshot 'testid=backend-selector'. The snapshot shows listbox with option "Connected Local", a separator, button "Add Backend" and button "Manage Backends". Screenshot with control-openhands browser screenshot --feature F25.selector-dropdown --name open. Hover opens it too: browser press Escape, move the pointer away with control-openhands browser hover 'testid=command-menu-trigger' (hovering a selector the pointer is already on does nothing), control-openhands browser hover 'testid=backend-selector', then the same snapshot shows combobox "Local" [expanded] and the two footer buttons.

Add Backend modalF25.add-backend-modalMapped behavior

Scenario F25--add-backend-modal · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Add backend menu item”.
  2. Do
    Press Tab.
  3. Do
    Press Shift+Tab.
  4. Do
    Press q.
  5. Do
    Press Escape.
  6. Do
    Choose “Add backend close”.
  7. Do
    Reload the page.
  8. Do
    Use browser focus as specified in the exact recipe.
  9. Do
    Press ArrowRight.

Expect

Add Backend opens "Choose how you want to connect" with a description, a Learn more link to the deployment docs and the tabs OpenHands Cloud (selected) and Agent-server (plus Providers with the bridge, see the Providers row). It takes keyboard focus on open (the close button), Tab and Shift+Tab wrap inside it, and at desktop width closing returns focus to the backend selector (at phone width the drawer closes with the modal and focus falls back to the page body). Escape does not close it; the X does.

Exact CLI recipe & bug checks

Run control-openhands browser click 'testid=add-backend-menu-item' and control-openhands browser snapshot 'testid=add-backend-modal': heading Choose how you want to connect, link Compare Cloud, self-hosted, and Enterprise deployment options (/url: https://docs.openhands.dev/overview/introduction, text Learn more) and tab OpenHands Cloud ... [selected] next to tab Agent-server .... Keyboard focus: control-openhands browser eval "(()=>{const a=document.activeElement;const d=document.querySelector('[data-testid=add-backend-modal]');return {tid:a.getAttribute('data-testid'),inDialog:!!(d&&d.contains(a))}})()" is {tid: "add-backend-close", inDialog: true} right after opening; after eight control-openhands browser press Tab and three browser press Shift+Tab (reading the same eval after each) inDialog stays true (Tab past Advanced wraps to the close button), and browser press q then control-openhands browser eval "document.querySelector('[data-testid=chat-input]').textContent" is "" (nothing reaches the Home composer behind). browser screenshot --feature F25.add-backend-modal --name focus-trap-open. control-openhands browser press Escape leaves browser count 'testid=add-backend-modal' at 1; control-openhands browser click 'testid=add-backend-close' makes it 0, and control-openhands browser eval "!!document.activeElement.closest('[data-testid=backend-selector]')" is true (focus is back on the selector). Keyboard focus in a light theme: arrange control-openhands browser eval "localStorage.setItem('openhands-color-theme','light-plus')" and browser reload, reopen the modal, run control-openhands browser focus 'testid=add-backend-option-cloud' and browser press ArrowRight, then control-openhands browser eval "getComputedStyle(document.activeElement).outlineColor". Expected: the theme's focus colour (--oh-focus, #007ACC in Light+). Connection tabs, the guidance toggle and the DigitalOcean provider choice use the theme focus role and preserve host --oh-focus overrides; default focus contrast is at least 3:1 against base, modal, raised and hovered neutral surfaces in all five themes. Solarized Light uses #227FBE. Check the settled outline after its transition, preserving the 2 px width and existing offsets. Restore with browser eval "localStorage.removeItem('openhands-color-theme')".

Advanced hostF25.cloud-advanced-hostMapped behavior

Scenario F25--advanced-host · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Backend selector”.
  2. Do
    Choose “Add backend menu item”.
  3. Do
    Choose “Add backend advanced toggle”.

Expect

on the Cloud tab, Advanced reveals a Host field (placeholder https://app.all-hands.dev) for a self-hosted Cloud.

Exact CLI recipe & bug checks

Reopen with browser click 'testid=backend-selector' and browser click 'testid=add-backend-menu-item', run control-openhands browser click 'testid=add-backend-advanced-toggle', then control-openhands browser attr 'testid=add-backend-advanced-toggle' aria-expanded (true) and control-openhands browser snapshot 'testid=add-backend-cloud-panel' (textbox Host with placeholder https://app.all-hands.dev).

Device flow errorF25.cloud-device-flowMapped behavior

Scenario F25--device-flow-error · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Enter “http://127.0.0.1:9” in “Add backend cloud host”.
  2. Do
    Choose “Add backend login”.
  3. Wait
    Wait for “Add backend auth error” to appear.
  4. Do
    Choose “Add backend auth retry”.
  5. Do
    Use browser tabs as specified in the exact recipe.
  6. Do
    Use browser close tab as specified in the exact recipe.
  7. Do
    Use browser tab as specified in the exact recipe.

Expect

Connect to OpenHands opens a popup and shows Starting authentication..., then Waiting for authorization... with the verification link and Cancel; Cancel returns to the idle button and closes the popup. An unreachable host shows Failed to start device flow: ... with Try again. Approving the code (adds a backend named OpenHands Cloud) needs a Cloud account.

Exact CLI recipe & bug checks

Run control-openhands browser fill 'testid=add-backend-cloud-host' http://127.0.0.1:9, control-openhands browser click 'testid=add-backend-login-button' --observe 'testid=add-backend-device-flow' and control-openhands browser wait 'testid=add-backend-auth-error' --timeout 15000. The observation goes Starting authentication... → Failed to start device flow: Failed to fetch / Try again, and control-openhands browser network --last 5 shows POST /oauth/device/authorize to http://127.0.0.1:9. control-openhands browser click 'testid=add-backend-auth-retry' starts again and fails the same way. Each attempt leaves an about:blank popup: control-openhands browser tabs, then control-openhands browser close-tab <i> for each extra page and control-openhands browser tab 0. Close the modal with testid=add-backend-close.

Device flow awaiting and CancelF25.cloud-device-flowMapped behavior

Scenario F25--device-flow-awaiting-and-cancel · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Add backend login”.
  2. Do
    Use browser tabs as specified in the exact recipe.
  3. Do
    Choose “Add backend auth cancel”.
  4. Do
    Use browser tabs as specified in the exact recipe.

Expect

Connect to OpenHands opens a popup and shows Starting authentication..., then Waiting for authorization... with the verification link and Cancel; Cancel returns to the idle button and closes the popup. An unreachable host shows Failed to start device flow: ... with Try again. Approving the code (adds a backend named OpenHands Cloud) needs a Cloud account.

Exact CLI recipe & bug checks

Reopen Add Backend (Cloud tab, default host), run control-openhands browser click 'testid=add-backend-login-button' --observe 'testid=add-backend-device-flow' --observe-ms 6000, then control-openhands browser snapshot 'testid=add-backend-device-flow'. If https://app.all-hands.dev is reachable (control-openhands browser network --external --last 5 lists it), the snapshot shows Waiting for authorization..., Browser opened. Complete sign-in to continue. If browser didn't open, visit:, a link https://app.all-hands.dev/oauth/device/verify?user_code=<code> and button Cancel; browser tabs lists a second page on that URL. Screenshot with --feature F25.cloud-device-flow --name awaiting, then control-openhands browser click 'testid=add-backend-auth-cancel': the panel is back to Connect to OpenHands / Advanced and browser tabs lists one page. Do not approve the code here (the Cloud backend bullet does, when it can). If Cloud is unreachable you get the error state of the previous bullet instead.

Agent-server guidanceF25.agent-server-guidanceMapped behavior

Scenario F25--agent-server-guidance · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Add backend option agent server”.
  2. Do
    Choose “Add backend local guidance toggle”.
  3. Do
    Choose “Add backend location option remote”.
  4. Do
    Choose “Add backend remote guidance toggle”.

Expect

the Agent-server tab has a Local / Remote toggle. Local shows a collapsible Before you connect with agent-canvas --backend-only --port 8001 and a local-setup docs link; Remote shows Recommended setup, Connection details and a remote-setup docs link.

Exact CLI recipe & bug checks

Run control-openhands browser click 'testid=add-backend-option-agent-server' and control-openhands browser snapshot 'testid=add-backend-agent-server-panel': radiogroup Agent-server location with Local checked, heading Before you connect, textboxes Host Name, Host, API Key and button "Connect" [disabled]. control-openhands browser click 'testid=add-backend-local-guidance-toggle' and browser snapshot 'testid=add-backend-local-guidance' show the code agent-canvas --backend-only --port 8001 and link Read the local backend setup guide. control-openhands browser click 'testid=add-backend-location-option-remote' and control-openhands browser click 'testid=add-backend-remote-guidance-toggle'; browser snapshot 'testid=add-backend-remote-guidance' shows Recommended setup, Connection details and Read the remote backend setup guide.

Connect rules and errorsF25.add-agent-serverKnown issue

Scenario F25--connect-rules-and-errors · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Enter “QA_Second” in “Add backend name”.
  2. Do
    Enter “127.0.0.1:9” in “Add backend host”.
  3. Do
    Enter “wrong-key” in “Add backend api key”.
  4. Do
    Choose “Add backend location option local”.
  5. Do
    Choose “Add backend submit”.
  6. Do
    Enter “” in “Add backend api key”.
  7. Do
    Choose “Add backend submit”.
  8. Wait
    Wait for “Add backend error” to appear.

Expect

Connect stays disabled until Host Name and a valid Host are filled; the API Key field is optional for Connect on Local and required on Remote, but a key-protected host still rejects an empty key. A dead host, an empty key or a wrong key shows Could not connect to <host> with the reason inline (Disconnected (check URL or network) for a dead host, Invalid API key for an empty or wrong key); a reachable host with the right key, entered on the default Local location, adds the backend, makes it active (BM-001) and redirects a /conversations/<id> page to /conversations without an error toast, also on the backend's first activation, without asking the new backend for the old conversation id.

Linked known issues

Known issue OpenHands/software-agent-sdk#5789 (F25.add-agent-server, F25.health-status): a deferred-init Agent Server can report ready after accepting a GUI origin through /api/init, while browser preflights from that origin still fail and Canvas cannot connect. Recheck an owned warm-pool server initialized with the actual non-localhost Canvas origin and compare admission with an origin supplied at server boot. The default loopback-origin stack is already allowed and cannot exercise this branch.

Exact CLI recipe & bug checks

Still on Remote: control-openhands browser fill 'testid=add-backend-name' QA_Second, control-openhands browser fill 'testid=add-backend-host' 127.0.0.1:9; control-openhands browser enabled 'testid=add-backend-submit' is false (Remote needs a key). control-openhands browser fill 'testid=add-backend-api-key' wrong-key makes it true. control-openhands browser click 'testid=add-backend-location-option-local' keeps the fields. control-openhands browser click 'testid=add-backend-submit' --observe 'testid=add-backend-submit' shows Checking…, then control-openhands browser text 'testid=add-backend-error' starts Could not connect to http://127.0.0.1:9 / Disconnected (check URL or network). .... Fill add-backend-host with <second-url> and submit again: the error reads Could not connect to <second-url> / Invalid API key. On Local the key is optional for Connect but not for a key-protected host: control-openhands browser fill 'testid=add-backend-api-key' '' leaves control-openhands browser enabled 'testid=add-backend-submit' at true (browser value 'testid=add-backend-api-key' reports length 0), and control-openhands browser click 'testid=add-backend-submit' then control-openhands browser wait 'testid=add-backend-error' --timeout 15000 shows the same Could not connect to <second-url> / Invalid API key (browser screenshot --feature F25.add-agent-server --name local-no-key-error). Close with testid=add-backend-close.

Add the second backendF25.add-agent-serverKnown failureKnown issue

Scenario F25--add-the-second-backend · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Open /conversations/<id>.
  2. Do
    Choose “Backend selector”.
  3. Do
    Choose “Add backend menu item”.
  4. Do
    Choose “Add backend option agent server”.
  5. Do
    Enter “the value specified in the recipe” in “Add backend api key”.
  6. Do
    Choose “Add backend submit”.
  7. Do
    Follow the onboarding setup in the recipe.
  8. Do
    Reload the page.

Expect

Connect stays disabled until Host Name and a valid Host are filled; the API Key field is optional for Connect on Local and required on Remote, but a key-protected host still rejects an empty key. A dead host, an empty key or a wrong key shows Could not connect to <host> with the reason inline (Disconnected (check URL or network) for a dead host, Invalid API key for an empty or wrong key); a reachable host with the right key, entered on the default Local location, adds the backend, makes it active (BM-001) and redirects a /conversations/<id> page to /conversations without an error toast, also on the backend's first activation, without asking the new backend for the old conversation id.

Known bug observations

Known failure (reproduced 2026-10-13, #18390): on the run's first activation of QA_Second the list sometimes holds GET <second-url>/api/conversations?ids=<id> (200); record the bullet as fail then (see Gotchas). (--external leaves out this stack's own GET /api/conversations?ids=<id> from the goto /conversations/<id> above, which the request log keeps across page loads; <second-url> is another origin, so a request to it is listed.) The new backend asks its own telemetry consent ("This preference is saved for the local backend “QA_Second”"): run control-openhands onboard --skip (it leaves the browser on /). After control-openhands browser reload the selector still reads QA_Second.

Linked known issues

Known issue OpenHands/software-agent-sdk#5789 (F25.add-agent-server, F25.health-status): a deferred-init Agent Server can report ready after accepting a GUI origin through /api/init, while browser preflights from that origin still fail and Canvas cannot connect. Recheck an owned warm-pool server initialized with the actual non-localhost Canvas origin and compare admission with an origin supplied at server boot. The default loopback-origin stack is already allowed and cannot exercise this branch.

Exact CLI recipe & bug checks

Run control-openhands browser goto /conversations/<id>, browser click 'testid=backend-selector', browser click 'testid=add-backend-menu-item', browser click 'testid=add-backend-option-agent-server', fill add-backend-name with QA_Second and add-backend-host with <second-url>, then control-openhands browser fill 'testid=add-backend-api-key' --value-file "$OH_VERIFY_RUN_2/private/session-key" and control-openhands browser click 'testid=add-backend-submit' --expect-url '/conversations(\?|$)'. The URL is /conversations, browser count 'testid=add-backend-modal' is 0, browser snapshot 'testid=backend-selector' shows combobox "QA_Second", and browser count 'testid=conversation-card' is 0. Before anything reloads the page, control-openhands browser toasts --history lists only Loading... and the new backend's Your LLM isn't set up yet, so conversations won't run. ... (the second stack has no LLM profile; no error toast), and control-openhands browser network --external --filter 'ids=' lists no request: the new backend is never asked for the first stack's conversation id. Known failure (reproduced 2026-10-13, #18390): on the run's first activation of QA_Second the list sometimes holds GET <second-url>/api/conversations?ids=<id> (200); record the bullet as fail then (see Gotchas). (--external leaves out this stack's own GET /api/conversations?ids=<id> from the goto /conversations/<id> above, which the request log keeps across page loads; <second-url> is another origin, so a request to it is listed.) The new backend asks its own telemetry consent ("This preference is saved for the local backend “QA_Second”"): run control-openhands onboard --skip (it leaves the browser on /). After control-openhands browser reload the selector still reads QA_Second.

Switch with the overlayF25.switch-backendKnown issue

Scenario F25--switch-with-the-overlay · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Backend selector”.
  2. Do
    Choose “Backend selector” / “Connected Local”.
  3. Do
    Choose “Conversation card”.
  4. Do
    Choose “Backend selector” / “Connected QA_Second”.

Expect

choosing another option shows a full-screen Switching to <name> overlay for about a second, then the sidebar and pages show that backend's data; on /conversations/<id> the app moves to /conversations (BM-002).

Linked known issues

Known issue #15822 (F25.switch-backend F25.health-status F25.cloud-org-rows): Cloud-proxy traffic was reported to continue after switching from Cloud to Local. Current source probes registered inactive Cloud backends on a five-minute cadence; recheck the reported repeated OPTIONS behavior with a real registered Cloud account/org and capture only credential-redacted request metadata after switching. This run has no authenticated Cloud backend.

Exact CLI recipe & bug checks

From / run browser click 'testid=backend-selector' and control-openhands browser click 'testid=backend-selector >> role=option[name="Connected Local"]' --observe 'testid=environment-switch-overlay' --observe-ms 2500. The observation shows Switching to Local, then <absent> about one second later; the selector reads Local and browser count 'testid=conversation-card' is 1. Then control-openhands browser click 'testid=conversation-card' --expect-url '/conversations/[0-9a-f-]+', open the selector and control-openhands browser click 'testid=backend-selector >> role=option[name="Connected QA_Second"]' --expect-url '/conversations(\?|$)': the URL is /conversations, the selector reads QA_Second and the card count is 0.

Pinned URLF25.backend-pinned-urlMapped behavior

Scenario F25--pinned-url · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Open /conversations/<id>?backend=default-local.
  2. Wait
    Wait for “Chat interface” to appear.
  3. Do
    Open /conversations?backend=qa-unknown-id.

Expect

in-app links carry ?backend=<id>; opening such a URL pins the tab to that backend; an unknown id is ignored.

Exact CLI recipe & bug checks

With QA_Second active run control-openhands browser goto '/conversations/<id>?backend=default-local' and control-openhands browser wait 'testid=chat-interface': the selector reads Local and the conversation opens. control-openhands browser goto '/conversations?backend=qa-unknown-id' keeps Local (card count 1). The sidebar card links themselves end in ?backend=default-local (browser snapshot).

Per-tab backendF25.per-tab-backendMapped behavior

Scenario F25--per-tab-backend · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Open /conversations.
  2. Do
    Use browser tab as specified in the exact recipe.
  3. Do
    Use browser tabs as specified in the exact recipe.
  4. Wait
    Wait for the element identified in the CLI recipe to appear.
  5. Do
    Use browser tab as specified in the exact recipe.
  6. Do
    Use browser tab as specified in the exact recipe.
  7. Wait
    Wait for “Backend selector” to appear.
  8. Do
    Choose “Backend selector”.
  9. Do
    Choose “Backend selector” / “Connected QA_Second”.
  10. Wait
    Wait for “Environment switch overlay” to disappear.
  11. Do
    Use browser tab as specified in the exact recipe.
  12. Do
    Use browser tab as specified in the exact recipe.
  13. Wait
    Wait for “Backend selector” to appear.
  14. Do
    Use browser tab as specified in the exact recipe.
  15. Do
    Choose “Conversation card”.
  16. Wait
    Wait for the element identified in the CLI recipe to appear.
  17. Do
    Use browser tab as specified in the exact recipe.
  18. Wait
    Wait for “Chat interface” to appear.
  19. Do
    Use browser close tab as specified in the exact recipe.
  20. Do
    Use browser close tab as specified in the exact recipe.
  21. Do
    Use browser close tab as specified in the exact recipe.
  22. Do
    Use browser tab as specified in the exact recipe.
  23. Do
    Use browser tabs as specified in the exact recipe.

Expect

the active backend is per browser tab: switching it in one tab leaves the other open tabs where they were, a new tab opened from a plain link starts from the last choice made in any tab, and a sidebar conversation row opened in a new tab (Control-click) lands on the backend that owns the conversation because its link carries ?backend=<owner>.

Exact CLI recipe & bug checks

With Local active and QA_Second registered, run control-openhands browser goto /conversations and control-openhands browser eval "JSON.parse(sessionStorage.getItem('openhands-active-backend')).backendId" (default-local); note QA_Second's id with control-openhands browser eval "JSON.parse(localStorage.getItem('openhands-backends')).find(b=>b.name==='QA_Second').id" (<second-id>, a generated UUID). Open a plain second tab, as a user opening the app again: control-openhands browser tab new /conversations prints index 1 and makes it the daemon's active tab (control-openhands browser tabs lists two pages at /conversations with active 1); control-openhands browser wait-tab '/conversations$' --new --timeout 5000 then prints index 0, the tab you came from, because --new leaves out the tab the daemon is on and that is now the new one (run from browser tab 0 it prints index 1, the second tab). control-openhands browser tab 1, control-openhands browser wait 'testid=backend-selector': browser snapshot 'testid=backend-selector' reads combobox "Local" and control-openhands browser eval "sessionStorage.getItem('openhands-active-backend')" is null (a new tab inherits nothing and starts from the localStorage fallback). Switch this tab: browser click 'testid=backend-selector', control-openhands browser click 'testid=backend-selector >> role=option[name="Connected QA_Second"]', control-openhands browser wait 'testid=environment-switch-overlay' --state detached --timeout 10000; the selector reads QA_Second, browser count 'testid=conversation-card' is 0 and control-openhands browser eval "JSON.parse(localStorage.getItem('openhands-active-backend')).backendId" is <second-id>. Back in control-openhands browser tab 0 nothing moved: the selector still reads Local, the card count is 1, the sessionStorage eval is still default-local while the localStorage eval is <second-id> (browser screenshot --feature F25.per-tab-backend --name first-tab-unchanged). A plain new tab starts from the last choice made in any tab: from tab 0 run control-openhands browser tab new /conversations (index 2, now active), browser wait 'testid=backend-selector': the selector reads QA_Second, the card count is 0, its sessionStorage item is null and the localStorage eval is <second-id> (--name plain-new-tab-last-choice). A conversation row opens on its owner instead: control-openhands browser tab 0, control-openhands browser click 'testid=conversation-card' --modifiers Control (add >> nth=0 when the run holds more conversations than the precondition's one: the click is strict), control-openhands browser wait-tab '/conversations/<id>' --timeout 20000 prints index 3 and the URL /conversations/<id>?backend=default-local; control-openhands browser tab 3, control-openhands browser wait 'testid=chat-interface' --timeout 30000, browser url still ends in /conversations/<id>?backend=default-local (no bounce to /conversations), the selector reads Local and browser toasts --history lists only Loading... (no This conversation does not exist…); browser screenshot --feature F25.per-tab-backend --name new-tab-on-owner. Close the extra tabs highest first: control-openhands browser close-tab 3, control-openhands browser close-tab 2, control-openhands browser close-tab 1, control-openhands browser tab 0; browser tabs lists one page and the selector reads Local. Opening the pinned link also recorded default-local as the last choice (the localStorage eval in tab 3 reads default-local), so run the plain-new-tab check before the row check.

Manage list and closingF25.manage-backendsMapped behavior

Scenario F25--manage-list-and-closing · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Backend selector”.
  2. Do
    Choose “Manage backends menu item”.
  3. Do
    Press Escape.
  4. Do
    Use browser mouse click as specified in the exact recipe.
  5. Do
    Choose “Close manage backends modal”.
  6. Do
    Choose “Manage backends done”.

Expect

Manage backends lists every backend with status dot, name, version (v1.50.1), host, status text and a LOCAL/CLOUD pill, plus Edit and Remove; it takes keyboard focus on open and keeps Tab inside; Escape, a backdrop click, X and Done close it and return focus to the selector.

Exact CLI recipe & bug checks

Run browser click 'testid=backend-selector', control-openhands browser click 'testid=manage-backends-menu-item' and control-openhands browser snapshot 'testid=manage-backends-modal': heading Manage backends, two rows Connected Local v1.50.1 <this run's URL> Connected Local and Connected QA_Second v1.50.1 <second-url> Connected Local, each with Edit and Remove, then Add Backend and Done. browser text 'testid=manage-backends-status-QA_Second' is Connected and browser text 'testid=manage-backends-version-QA_Second' is v1.50.1 (whatever doctor reports). Screenshot with --feature F25.manage-backends --name list. Focus is inside: the Add Backend bullet's focus eval with manage-backends-modal in place of add-backend-modal reads close-manage-backends-modal on open and stays inDialog true over nine Tabs and two Shift+Tabs (with two backends listed, Tab 8 reaches Done and Tab 9 wraps to the close button); after Escape, focus is back on the selector. Close it four ways, reopening between: browser press Escape, control-openhands browser mouse-click 20 500 (backdrop), browser click 'testid=close-manage-backends-modal', browser click 'testid=manage-backends-done'; each time browser count 'testid=manage-backends-modal' is 0.

Select from ManageF25.manage-selectMapped behavior

Scenario F25--select-from-manage · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Conversation card”.
  2. Do
    Choose “Manage backends row qa second” / the button / match 1.

Expect

clicking a healthy row makes that backend active and closes the modal; on a detail page it leaves it for that section's list, like the selector does (BM-002).

Exact CLI recipe & bug checks

With Local active, control-openhands browser click 'testid=conversation-card' --expect-url '/conversations/[0-9a-f-]+', open Manage Backends, then control-openhands browser click 'testid=manage-backends-row-QA_Second >> role=button >> nth=0' --expect-url '/conversations(\?|$)'. Like the selector, the app leaves the detail page for /conversations (BM-002): browser count 'testid=manage-backends-modal' is 0, the selector reads QA_Second, browser count 'testid=chat-interface' and browser count 'testid=conversation-card' are 0, and browser screenshot --feature F25.manage-select --name after-select shows QA_Second's Home with its empty conversation list.

Edit validation and errorsF25.edit-backendMapped behavior

Scenario F25--edit-validation-and-errors · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Manage backends edit qa second”.
  2. Do
    Use browser focus as specified in the exact recipe.

Expect

the pencil opens "Edit backend" pre-filled (key masked) with a Connected · Local v<version> badge; empty name and invalid host show Name is required and Enter a valid URL (e.g. http://localhost:8080) and disable Save; Save re-tests the connection, keeps the modal open with Could not connect to <host> on failure, and persists on success.

Exact CLI recipe & bug checks

Open Manage Backends, control-openhands browser click 'testid=manage-backends-edit-QA_Second', then browser value 'testid=edit-backend-host' (<second-url>), browser attr 'testid=edit-backend-api-key' type (password) and browser text 'testid=edit-backend-status' (Connected, ·, Local, v1.50.1 on separate lines). Fill edit-backend-name with an empty string and edit-backend-host with not a host, then browser focus 'testid=edit-backend-api-key' and browser snapshot 'testid=edit-backend-form': alerts Name is required and Enter a valid URL (e.g. http://localhost:8080); browser enabled 'testid=edit-backend-submit' is false.

Escape in EditF25.edit-escapeKnown failure

Scenario F25--escape-in-edit · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Press Escape.

Expect

Escape in the Edit modal should not discard the draft (the modal disables Escape).

Known bug observations

Known failure, see Gotchas.

Linked known issues

Known failure (repro candidate, not an exemption): F25.edit-escape closes the Manage modal underneath and drops the Edit draft (Cancel and X close only Edit, F25.edit-cancel) (#17953).

Exact CLI recipe & bug checks

In the same Edit modal press control-openhands browser press Escape. Expected: the Edit modal stays (it disables Escape) or only it closes. Today browser count 'testid=edit-backend-modal' and browser count 'testid=manage-backends-modal' are both 0: the whole stack closes and the draft is lost. Nothing was saved (browser snapshot 'testid=backend-selector' still reads QA_Second).

Edit and saveF25.edit-backendMapped behavior

Scenario F25--edit-and-save · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Edit backend submit”.
  2. Wait
    Wait for “Edit backend error” to appear.
  3. Wait
    Wait for “Edit backend modal” to disappear.
  4. Do
    Reload the page.

Expect

the pencil opens "Edit backend" pre-filled (key masked) with a Connected · Local v<version> badge; empty name and invalid host show Name is required and Enter a valid URL (e.g. http://localhost:8080) and disable Save; Save re-tests the connection, keeps the modal open with Could not connect to <host> on failure, and persists on success.

Exact CLI recipe & bug checks

Reopen Manage Backends and the pencil. Fill edit-backend-host with http://127.0.0.1:9, browser click 'testid=edit-backend-submit', browser wait 'testid=edit-backend-error' --timeout 10000: the text starts Could not connect to http://127.0.0.1:9 and the modal stays. Fill the host back with <second-url> and edit-backend-name with QA_Renamed, click edit-backend-submit, then control-openhands browser wait 'testid=edit-backend-modal' --state detached --timeout 10000; browser count 'testid=manage-backends-row-QA_Renamed' is 1. Click manage-backends-done, browser reload: the selector reads QA_Renamed.

Health statusF25.health-statusKnown issue

Scenario F25--health-status · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Setup
    Change the run-owned service state: stop agent-server --run $OH_VERIFY_RUN_2.
  2. Wait
    Wait for “Backend selector” / “Disconnected” to appear.
  3. Do
    Choose “Backend selector”.

Expect

a backend that stops answering turns its dot red (Disconnected) within one health probe (every 30 s for local and remote agent servers; Cloud backends every 5 min and not on window focus or reconnect, #18128); its Manage row reads Disconnected (check URL or network) with a red detail line and cannot be selected.

Linked known issues

Known issue #15822 (F25.switch-backend F25.health-status F25.cloud-org-rows): Cloud-proxy traffic was reported to continue after switching from Cloud to Local. Current source probes registered inactive Cloud backends on a five-minute cadence; recheck the reported repeated OPTIONS behavior with a real registered Cloud account/org and capture only credential-redacted request metadata after switching. This run has no authenticated Cloud backend.

Known issue OpenHands/software-agent-sdk#5789 (F25.add-agent-server, F25.health-status): a deferred-init Agent Server can report ready after accepting a GUI origin through /api/init, while browser preflights from that origin still fail and Canvas cannot connect. Recheck an owned warm-pool server initialized with the actual non-localhost Canvas origin and compare admission with an origin supplied at server boot. The default loopback-origin stack is already allowed and cannot exercise this branch.

Exact CLI recipe & bug checks

With QA_Renamed the active backend (as the Edit bullet's reload leaves it; the closed selector shows only the active backend's dot), run control-openhands service stop agent-server --run "$OH_VERIFY_RUN_2", then on the open page control-openhands browser wait 'testid=backend-selector >> role=status[name="Disconnected"]' --timeout 40000. browser click 'testid=backend-selector' and browser snapshot 'testid=backend-selector' show option "Disconnected QA_Renamed" next to option "Connected Local". In Manage Backends browser text 'testid=manage-backends-status-QA_Renamed' is Disconnected (check URL or network), testid=manage-backends-status-detail-QA_Renamed repeats it with Check that the backend URL is correct ..., and browser enabled 'testid=manage-backends-row-QA_Renamed >> role=button >> nth=0' is false. An error toast with the same text also appears. Screenshot with --feature F25.health-status --name manage-down.

Recovery gateF25.recovery-gateMapped behavior

Scenario F25--recovery-gate · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Reload the page.
  2. Wait
    Wait for “Manage backends modal” to appear.
  3. Do
    Press Escape.
  4. Do
    Choose “Manage backends row local” / the button / match 1.
  5. Wait
    Wait for “Agent server onboarding screen” to disappear.

Expect

reloading while the active backend is down shows only agent-server-onboarding-screen with Manage backends in recovery mode (no X, no Done, Escape ignored, primary Add Backend); choosing a healthy row restores the app.

Exact CLI recipe & bug checks

Close the modal, control-openhands browser reload, then control-openhands browser wait 'testid=manage-backends-modal' --timeout 20000. browser testids lists agent-server-onboarding-screen and the Manage rows but no sidebar; browser count 'testid=close-manage-backends-modal' and browser count 'testid=manage-backends-done' are 0, and browser press Escape leaves the modal (count 1). control-openhands browser click 'testid=manage-backends-row-Local >> role=button >> nth=0' and browser wait 'testid=agent-server-onboarding-screen' --state detached --timeout 15000 restore the app: selector Local, card count 1. Screenshot the gate with --feature F25.recovery-gate --name gate before choosing.

Remove the active backendF25.remove-backendMapped behavior

Scenario F25--remove-the-active-backend · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Restart the run-owned stack as specified.
  2. Do
    Choose “Backend selector”.
  3. Wait
    Wait for “Backend selector” / “Connected QA_Renamed” to appear.
  4. Do
    Choose “Manage backends remove qa renamed”.
  5. Do
    Choose “Confirmation dialog” / “Cancel”.
  6. Do
    Choose “Confirmation dialog” / “Confirm”.
  7. Do
    Reload the page.

Expect

the trash icon asks Remove backend "<name>"? If it is active, the app will switch back to Local.; Cancel keeps it, Confirm removes it and an active removed backend falls back to Local (BM-003).

Exact CLI recipe & bug checks

Run control-openhands restart --run "$OH_VERIFY_RUN_2", browser click 'testid=backend-selector', browser wait 'testid=backend-selector >> role=option[name="Connected QA_Renamed"]' --timeout 25000 and click that option (selector reads QA_Renamed). Open Manage Backends, control-openhands browser click 'testid=manage-backends-remove-QA_Renamed'; browser text 'testid=confirmation-modal' reads Remove backend "QA_Renamed"? If it is active, the app will switch back to Local. control-openhands browser click 'testid=confirmation-modal >> role=button[name="Cancel"]' keeps the row (count 'testid=manage-backends-row-QA_Renamed' 1). Click the trash again, then control-openhands browser click 'testid=confirmation-modal >> testid=confirm-button': the row count is 0. Click Done and browser reload: the selector reads Local, browser count 'testid=backend-selector >> role=option' (dropdown open) is 1, and the card count is 1.

Add from Manage, Edit Cancel and XF25.manage-add · F25.edit-cancelKnown failure

Scenario F25--add-from-manage-edit-cancel-and-x · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Open /conversations/<id>.
  2. Wait
    Wait for “Chat interface” to appear.
  3. Do
    Choose “Manage backends add”.
  4. Wait
    Wait for “Add backend modal” to appear.
  5. Do
    Choose “Add backend close”.
  6. Do
    Choose “Add backend submit”.
  7. Do
    Choose “Manage backends edit qa second”.
  8. Do
    Enter “QA_Draft” in “Edit backend name”.
  9. Do
    Choose “Edit backend cancel”.
  10. Do
    Choose “Edit backend close”.
  11. Do
    Choose “Manage backends remove qa second”.
  12. Do
    Choose “Confirmation dialog” / “Confirm”.
  13. Do
    Choose “Manage backends done”.
  14. Do
    Reload the page.

Expect

Add Backend in Manage backends opens the same Add form stacked over Manage; its X returns to Manage, and a successful add keeps Manage open with the new row while the new backend becomes active.

Cancel and the X in Edit backend close only the Edit modal and discard the draft; Manage backends stays open with the stored values.

Linked known issues

Known failure (repro candidate, not an exemption): F25.edit-escape closes the Manage modal underneath and drops the Edit draft (Cancel and X close only Edit, F25.edit-cancel) (#17953).

Exact CLI recipe & bug checks

The second stack is running again after the previous bullet. Run browser goto /conversations/<id>, browser wait 'testid=chat-interface', open the selector, click manage-backends-menu-item, then control-openhands browser click 'testid=manage-backends-add' and control-openhands browser wait 'testid=add-backend-modal' --timeout 5000 (visible); browser count 'testid=manage-backends-modal' is 1 underneath. browser click 'testid=add-backend-close': the Add count is 0 and the Manage count stays 1. Click manage-backends-add again, add-backend-option-agent-server, fill add-backend-name with QA_Second, add-backend-host with <second-url> and add-backend-api-key with --value-file "$OH_VERIFY_RUN_2/private/session-key", then browser click 'testid=add-backend-submit' --expect-url '/conversations(\?|$)': the URL is /conversations, count 'testid=add-backend-modal' is 0, count 'testid=manage-backends-modal' is still 1, count 'testid=manage-backends-row-QA_Second' is 1 and the selector reads QA_Second (screenshot --feature F25.manage-add --name after-add). No consent prompt this time: it is stored on the second backend. Then control-openhands browser click 'testid=manage-backends-edit-QA_Second', browser fill 'testid=edit-backend-name' QA_Draft and control-openhands browser click 'testid=edit-backend-cancel': count 'testid=edit-backend-modal' 0, count 'testid=manage-backends-modal' 1, row QA_Second 1, row QA_Draft 0. Repeat with control-openhands browser click 'testid=edit-backend-close': same counts, and reopening the pencil shows browser value 'testid=edit-backend-name' QA_Second; leave with edit-backend-cancel. Remove it again: browser click 'testid=manage-backends-remove-QA_Second', browser click 'testid=confirmation-modal >> testid=confirm-button', browser click 'testid=manage-backends-done' and browser reload: the selector reads Local.

Collapsed railF25.collapsed-entryMapped behavior

Scenario F25--collapsed-rail · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Sidebar collapse toggle”.
  2. Do
    Hover over “Collapsed backend selector”.
  3. Do
    Choose “Add backend menu item”.
  4. Wait
    Wait for “Add backend modal” to appear.
  5. Wait
    Wait for “Manage backends modal” to appear.
  6. Do
    Choose “Sidebar collapse toggle”.

Expect

with the rail collapsed, the backend icon's popover offers the same Add Backend and Manage Backends items, and both modals open.

Exact CLI recipe & bug checks

Run control-openhands browser click 'testid=sidebar-collapse-toggle' (attr 'aside[data-collapsed]' data-collapsed is true), control-openhands browser hover 'testid=collapsed-backend-selector-link', control-openhands browser click 'testid=add-backend-menu-item' and control-openhands browser wait 'testid=add-backend-modal' --timeout 5000 (visible). Close it, hover again, click manage-backends-menu-item and browser wait 'testid=manage-backends-modal' --timeout 5000. Close with Done and expand with browser click 'testid=sidebar-collapse-toggle'.

PhoneF25.phoneMapped behavior

Scenario F25--phone · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Setup
    Use the phone viewport.
  2. Do
    Open /conversations.
  3. Do
    Choose “Sidebar mobile menu toggle”.
  4. Do
    Choose “Sidebar mobile drawer” / “Backend selector”.
  5. Do
    Choose “Add backend menu item”.
  6. Do
    Choose “Add backend option agent server”.
  7. Setup
    Use the 320x568 viewport.
  8. Setup
    Use the 320x400 viewport.
  9. Do
    Scroll “Edit backend form” as specified in the recipe.
  10. Setup
    Use the desktop viewport.

Expect

at 390 px the drawer's selector opens Add and Manage inside the viewport without horizontal overflow. Edit backend fits at 320 and 390 px; on short viewports its form scrolls while the title and close control stay visible, and Cancel/Save remain reachable.

Exact CLI recipe & bug checks

Run control-openhands browser viewport phone, browser goto /conversations, control-openhands browser click 'testid=sidebar-mobile-menu-toggle', control-openhands browser click 'testid=sidebar-mobile-drawer >> testid=backend-selector', browser click 'testid=add-backend-menu-item', browser click 'testid=add-backend-option-agent-server' and control-openhands browser bbox 'testid=add-backend-modal': insideViewport true, pageHorizontalOverflow false. Screenshot --feature F25.phone --name add-modal. Close it (the drawer stays open), open Manage Backends from the drawer selector the same way and check bbox 'testid=manage-backends-modal' the same. Click testid=manage-backends-edit-Local and check bbox 'testid=edit-backend-modal': insideViewport true; repeat at browser viewport 320x568. At browser viewport 320x400, clear edit-backend-name, fill edit-backend-host with not a host and focus edit-backend-api-key to show both validation messages. Run browser scroll 'testid=edit-backend-form' --by 600: Cancel and Save must come into view while the title and edit-backend-close stay visible. Check the modal and all three controls with bbox, then close with Cancel. Return with browser viewport desktop; reopen Edit and confirm its width is still 520px.

Providers tabF25.providers-tabKnown failure

Scenario F25--providers-tab · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Add backend option providers”.

Expect

only when the page has the Managed Agents bridge (window.marsBridge), Add Backend shows a third tab Providers ("Connect through DigitalOcean and more.") with a DigitalOcean card ("Your team's OpenHands agents") and More providers coming soon.. Every launcher-served web page has it (the ingress answers /mars/health); without it (MARS_WEB=0, or a non-loopback bind with no session key at all) there are only the Cloud and Agent-server tabs and no Managed Agents UI anywhere. A non-loopback bind that has a key (F26 --host 0.0.0.0 with LOCAL_BACKEND_API_KEY) mounts the bridge behind that key and offers the tab too, although the page itself carries no key (see the sign-in bullet's

Known bug observations

Known failure).

Exact CLI recipe & bug checks

On the default run browser eval "!!window.marsBridge" is true (Preconditions); curl -s "http://127.0.0.1:$(control-openhands status | jq -r .ports.frontend)/mars/health" (the static child behind the ingress) returns the HTML index, not JSON: one bridge per stack. Open Add Backend: the tablist adds tab "Providers Connect through DigitalOcean and more." (testid=add-backend-option-providers); control-openhands browser click 'testid=add-backend-option-providers' and browser snapshot 'testid=add-backend-providers-panel' show button "DigitalOcean Your team's OpenHands agents" and More providers coming soon. (browser screenshot --feature F25.add-backend-modal --name with-providers-tab). At phone width the modal stays inside the viewport with the three tab titles cut to Ope…, Age…, Pro… (--feature F25.providers-tab --name phone-tabs).

DigitalOcean sign-inF25.digitalocean-sign-inKnown failure

Scenario F25--digitalocean-sign-in · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Add backend provider digitalocean”.
  2. Wait
    Wait for “Managed agents sign in” to appear.
  3. Do
    Choose “Add backend providers back”.

Expect

the DigitalOcean card opens "Connect your DigitalOcean team" with a Back button, a masked Personal access token field, a Create a token link to https://cloud.digitalocean.com/account/api/tokens and Connect with token, disabled until a token is typed (the Sign in with DigitalOcean OAuth button exists only in the desktop app). A token of the wrong shape reads That does not look like a DigitalOcean personal access token.; a well-formed token DigitalOcean rejects shows its HTTP error (401 Unauthorized). Back returns to the Providers tab.

Known bug observations

Known failure (reproduced 2026-10-14, #18434): every token, even the malformed one, ends in session key required (POST /mars/rpc/getAuthState and savePat 401), because the web bridge client sends only the baked page key, never the typed one; it predates #18349 (BASE with MARS_WEB=1 fails the same way). A --frontend-only page connected to another stack's backend fails the same way for a different reason (the typed key belongs to the other stack); #18434 leaves that case to a maintainer decision.

Exact CLI recipe & bug checks

On the default run, run control-openhands browser click 'testid=add-backend-provider-digitalocean' and browser wait 'testid=managed-agents-sign-in': heading Connect your DigitalOcean team, textbox "Personal access token" (browser attr 'testid=managed-agents-token' type is password), link Create a token (/url: https://cloud.digitalocean.com/account/api/tokens) and button "Connect with token" [disabled]; no OAuth button in a browser (browser count 'testid=managed-agents-oauth' is 0). Fill testid=managed-agents-token with dop_v1_qa_invalid_token_0000 and click testid=managed-agents-token-submit: browser text 'testid=managed-agents-auth-error' reads That does not look like a DigitalOcean personal access token. (POST /mars/rpc/savePat 500). Fill a well-formed dummy, dop_v1_ plus 64 zeros, and submit again: after a few seconds the error reads 401 Unauthorized (POST /mars/rpc/savePat 401, DigitalOcean's answer; browser screenshot --feature F25.digitalocean-sign-in --name rejected-token). Never type a real token here. control-openhands browser click 'testid=add-backend-providers-back' returns to the Providers panel (browser count 'testid=managed-agents-sign-in' is 0; the providers panel itself stays mounted behind the sign-in screen, so its count is 1 either way); close with testid=add-backend-close. At phone width the sign-in screen fits (bbox 'testid=add-backend-modal' insideViewport true). Both errors also raise an error toast with the same text. Expected on a page served without a baked key (F26 --host 0.0.0.0, Docker), once the server's key was typed on the API-key screen: the same two answers as above. Known failure (reproduced 2026-10-14, #18434): every token, even the malformed one, ends in session key required (POST /mars/rpc/getAuthState and savePat 401), because the web bridge client sends only the baked page key, never the typed one; it predates #18349 (BASE with MARS_WEB=1 fails the same way). A --frontend-only page connected to another stack's backend fails the same way for a different reason (the typed key belongs to the other stack); #18434 leaves that case to a maintainer decision.

Managed Agents with an accountF25.digitalocean-agents · F25.digitalocean-backend-removalBlocked

Scenario F25--managed-agents-with-an-account · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Blocked: needs a DigitalOcean account with Managed Agents. With one, after a real token the panel lists agents (digitalocean-agent-<id>), New Agent opens digitalocean-new-agent-form (name, size), a session's Connect adds a backend and the sidebar shows mars-session-panel; removing that backend opens mars-backend-removal-modal with disconnect-only-button and stop-session-button.

Expect

signed in, the panel lists the team's OpenHands agents and their sessions, New Agent (name and sandbox size), refresh and sign-out; connecting a session (or Resume and connect a paused one) adds it as the active backend, the sidebar's conversation list becomes that agent's session panel (mars-session-panel), and Manage Backends' Managed Agents view offers Pause, Stop, Disconnect and Open per session (blocked: needs a DigitalOcean account with Managed Agents).

removing a DigitalOcean backend in Manage Backends explains that it only disconnects and offers Disconnect only or Stop session (blocked: needs a connected DigitalOcean session).

Exact CLI recipe & bug checks

Blocked: needs a DigitalOcean account with Managed Agents. With one, after a real token the panel lists agents (digitalocean-agent-<id>), New Agent opens digitalocean-new-agent-form (name, size), a session's Connect adds a backend and the sidebar shows mars-session-panel; removing that backend opens mars-backend-removal-modal with disconnect-only-button and stop-session-button.

Cloud backendF25.cloud-device-flow · F25.cloud-org-rows · F25.cloud-settings-link · F25.share-publicly · F25.cloud-sandbox-statesKnown issueBlocked

Scenario F25--cloud-backend · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Choose “Add backend login”.
  2. Do
    Use browser tab as specified in the exact recipe.
  3. Do
    Choose “Authorize Device”.
  4. Do
    Use browser tab as specified in the exact recipe.
  5. Wait
    Wait for the element identified in the CLI recipe to appear.
  6. Do
    Use browser close tab as specified in the exact recipe.
  7. Do
    Use browser tab as specified in the exact recipe.
  8. Do
    Choose “Submit”.
  9. Do
    Reload the page.

Expect

Connect to OpenHands opens a popup and shows Starting authentication..., then Waiting for authorization... with the verification link and Cancel; Cancel returns to the idle button and closes the popup. An unreachable host shows Failed to start device flow: ... with Try again. Approving the code (adds a backend named OpenHands Cloud) needs a Cloud account.

each Cloud backend appears once per organization as <name> – <org> (Personal Workspace for the user's own), and choosing one scopes lists to that org.

with a Cloud backend active the gear next to the selector opens <cloud host>/settings?org=<org> in a new tab.

on a Cloud backend the conversation menu has a Public Share toggle with copy-link and open-link buttons; on a local backend the item is absent.

Cloud conversations show waiting, archived and error sandbox states in chat.

Linked known issues

Known issue #17390 (F25.cloud-sandbox-states): Every sent Cloud message can fail with token_not_found_in_db from the model proxy. Recheck with the affected Cloud account/session and a minimal no-tools prompt; capture the error type with provider token text and hashes redacted. The report supplies no deployment version or deterministic token-provisioning sequence, and this run has no Cloud account.

Known issue #17163 (F25.cloud-sandbox-states): A Free Cloud account can lose access to previously usable models with HTTP 403 and a team-restriction message despite no team being created. Recheck with the affected account and its model entitlement/org history; local credentials cannot prove Cloud eligibility and this run has no such account.

Known issue #16874 (F25.cloud-sandbox-states): A failed resume of a paused Cloud sandbox was reported to consume a one-shot guard and leave chat disconnected until remount. Current source uses a retrying resume hook, so recheck the original failure and a second pause in one route mount before concluding resolution. Blocked without a real paused Cloud sandbox and a controlled one-request backend/network failure; source changes alone are not a live pass.

Known issue #15676 (F25.cloud-sandbox-states): Refreshing an approximately 24-hour-old Cloud conversation on Vercel can fail to resume/reconnect. Recheck an authenticated affected Cloud deployment with an aged sandbox/session and exact console/network error; the report omits its commit and failure text, and no aged Cloud fixture is available here.

Known issue #15822 (F25.switch-backend F25.health-status F25.cloud-org-rows): Cloud-proxy traffic was reported to continue after switching from Cloud to Local. Current source probes registered inactive Cloud backends on a five-minute cadence; recheck the reported repeated OPTIONS behavior with a real registered Cloud account/org and capture only credential-redacted request metadata after switching. This run has no authenticated Cloud backend.

Known issue OpenHands/automation#565 (F25.cloud-org-rows, F25.cloud-org-suspended): An org-scoped Cloud automation request with an unauthorized X-Org-Id can turn the identity service's 403 into a misleading 502/outage page. Recheck with an authenticated Cloud org-bound key and a disposable unauthorized organization id, comparing automation list and the identity response while preserving credential redaction. Local mode bypasses this identity service and cannot reproduce it.

Exact CLI recipe & bug checks

Only with a Cloud account (Preconditions); it creates a device grant and one conversation on that account, and shows the account's own conversations in the sidebar (keep those screenshots private). Reopen Add Backend on the Cloud tab, browser click 'testid=add-backend-login-button', then control-openhands browser tab 1 (the verify popup: Device Authorization Request, the code, Authorize Device), control-openhands browser click 'role=button[name="Authorize Device"]' (Success!) and control-openhands browser tab 0. Within about 15 s (token polling answers 400 until approved, then 200) browser count 'testid=add-backend-modal' is 0, the popup is gone and browser snapshot 'testid=backend-selector' reads combobox "OpenHands Cloud": OpenHands Cloud – Personal Workspace; the registry holds {name: "OpenHands Cloud", host: "https://app.all-hands.dev", kind: "cloud"} (browser eval "JSON.parse(localStorage.getItem('openhands-backends')).map(b=>({name:b.name,host:b.host,kind:b.kind}))"). Open the selector: option "Connected OpenHands Cloud – Personal Workspace" (one option per organization) next to Connected Local; Manage Backends shows the row with Personal Workspace and the Cloud pill. browser attr 'testid=backend-selector-settings-link' href is https://app.all-hands.dev/settings?org=<org id> with target _blank; clicking it opens that tab (browser wait-tab 'app.all-hands.dev/settings', then browser close-tab 1, browser tab 0). Start one conversation from Home: fill testid=chat-input with Reply with exactly: QA_F25_CLOUD. Do not run any tools. and browser click 'testid=submit-button' --expect-url '/conversations/': the URL is /conversations/task-<id> while the sandbox starts (toasts Creating conversation…, Sending...), then moves to /conversations/<id>?backend=<cloud id>&org=<org id> and the reply QA_F25_CLOUD appears (about a minute). Its conversation-name >> ellipsis-button menu has share-publicly-button (Public Share, off); do not flip it unless the conversation may be public. Delete it with delete-button and 'role=dialog >> testid=confirm-button', then remove the Cloud backend in Manage Backends (manage-backends-remove-OpenHands Cloud, confirm): after browser reload the selector reads Local.

Device verify pageF25.device-verify-pageMapped behavior

Scenario F25--device-verify-page · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Open /oauth/device/verify.
  2. Do
    Enter “QA-0000” in “Device Code:”.
  3. Do
    Choose “Continue”.
  4. Do
    Open /oauth/device/verify?user_code=QA-0000.
  5. Do
    Choose “Cancel”.
  6. Do
    Choose “Authorize Device”.
  7. Wait
    Wait for “Error” to appear.

Expect

/oauth/device/verify shows a "Device Authorization" code form; with ?user_code= it shows "Device Authorization Request", the code, a Security Notice and Cancel / Authorize Device. On a local stack Authorize and Continue end in the Error card with Try Again.

Exact CLI recipe & bug checks

Run control-openhands browser goto /oauth/device/verify and browser snapshot: heading Device Authorization, Enter the code displayed on your device:, textbox Device Code: and button Continue, inside the normal app shell. control-openhands browser fill 'role=textbox[name="Device Code:"]' QA-0000 and browser click 'role=button[name="Continue"]' go straight to the result: heading Error, Failed to authorize device. Please try again., button Try Again; browser network --filter verify-authenticated shows POST /oauth/device/verify-authenticated 404. Then control-openhands browser goto '/oauth/device/verify?user_code=QA-0000': heading Device Authorization Request, DEVICE CODE, QA-0000, Security Notice, buttons Cancel and Authorize Device (screenshot --feature F25.device-verify-page --name with-code). browser click 'role=button[name="Cancel"]' does nothing in a tab the page did not open (URL unchanged). browser click 'role=button[name="Authorize Device"]' and browser wait 'role=heading[name="Error"]' show the same Error card; Try Again reloads to the request.

Shared viewF25.shared-conversation-viewMapped behavior

Scenario F25--shared-view · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Open /shared/conversations/<id>.
  2. Wait
    Wait for the element identified in the CLI recipe to appear.

Expect

/shared/conversations/<id> is a standalone read-only page (no app shell); on a local backend it reads Conversation not found.

Exact CLI recipe & bug checks

Run control-openhands browser goto /shared/conversations/<id> and control-openhands browser wait-text 'Conversation not found' --timeout 15000. The page has no sidebar; browser network --filter shared shows GET /api/shared-conversations and /api/shared-events/search 404. Screenshot --feature F25.shared-conversation-view --name local-not-found.

No Public Share locallyF25.share-publiclyBlocked

Scenario F25--no-public-share-locally · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Open /conversations/<id>.
  2. Do
    Choose “Conversation name” / “More options”.

Expect

on a Cloud backend the conversation menu has a Public Share toggle with copy-link and open-link buttons; on a local backend the item is absent.

Exact CLI recipe & bug checks

Run browser goto /conversations/<id>, control-openhands browser click 'testid=conversation-name >> testid=ellipsis-button' and control-openhands browser count 'testid=share-publicly-button': 0 on a local backend (browser testids --filter button lists rename-button, show-skills-button, show-agent-tools-button, export-transcript-button, download-trajectory-button, display-cost-button and delete-button; show-hooks-button and stop-button appear only while the conversation is active (idle, running, waiting for confirmation or finished), so a paused or errored conversation has neither). The toggle, copy-share-link-button and open-share-link-button need a Cloud backend: blocked.

CleanupF25 · setup / cleanupSupport step

Scenario F25--cleanup · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Press Escape.
  2. Do
    Stop the run-owned stack.

Expect

Complete this support step before continuing to the next scenario.

Exact CLI recipe & bug checks

browser press Escape, then control-openhands stop --run "$OH_VERIFY_RUN_2" and unset OH_VERIFY_RUN_2. The conversation stays with this run's state.

ErrorsF25 · setup / cleanupSupport step

Scenario F25--errors · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    control-openhands browser errors --app-only after the family shows no page errors. Expected app-origin entries: POST /oauth/device/verify-authenticated 404 (twice), the shared-view 404s, the warning Scripts may close only the windows that were opened by them. from the device-verify Cancel, and a burst of CORS console.errors for <second-url> while the second agent server is stopped or restarting (see Gotchas).

Expect

Complete this support step before continuing to the next scenario.

Exact CLI recipe & bug checks

control-openhands browser errors --app-only after the family shows no page errors. Expected app-origin entries: POST /oauth/device/verify-authenticated 404 (twice), the shared-view 404s, the warning Scripts may close only the windows that were opened by them. from the device-verify Cancel, and a burst of CORS console.errors for <second-url> while the second agent server is stopped or restarting (see Gotchas).

Same-origin credential readabilityF25.manage-backends · F25.add-agent-serverKnown failureKnown issue

Scenario F25--same-origin-credential-readability · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    In an authenticated private run, use read-only browser eval to return only the count of registered entries and the count whose apiKey is a nonempty string in JSON.parse(localStorage.getItem('openhands-backends') || '[]'). Expected: backend credentials are not readable by arbitrary same-origin script. Known failure, reproduced 2026-10-12: registered entries have readable plaintext keys (#16492). Retain counts/booleans only, never key values or whole storage JSON.

Expect

Manage backends lists every backend with status dot, name, version (v1.50.1), host, status text and a LOCAL/CLOUD pill, plus Edit and Remove; it takes keyboard focus on open and keeps Tab inside; Escape, a backdrop click, X and Done close it and return focus to the selector.

Connect stays disabled until Host Name and a valid Host are filled; the API Key field is optional for Connect on Local and required on Remote, but a key-protected host still rejects an empty key. A dead host, an empty key or a wrong key shows Could not connect to <host> with the reason inline (Disconnected (check URL or network) for a dead host, Invalid API key for an empty or wrong key); a reachable host with the right key, entered on the default Local location, adds the backend, makes it active (BM-001) and redirects a /conversations/<id> page to /conversations without an error toast, also on the backend's first activation, without asking the new backend for the old conversation id.

Known bug observations

Known failure, reproduced 2026-10-12: registered entries have readable plaintext keys (#16492). Retain counts/booleans only, never key values or whole storage JSON.

Linked known issues

Known issue OpenHands/software-agent-sdk#5789 (F25.add-agent-server, F25.health-status): a deferred-init Agent Server can report ready after accepting a GUI origin through /api/init, while browser preflights from that origin still fail and Canvas cannot connect. Recheck an owned warm-pool server initialized with the actual non-localhost Canvas origin and compare admission with an origin supplied at server boot. The default loopback-origin stack is already allowed and cannot exercise this branch.

Exact CLI recipe & bug checks

In an authenticated private run, use read-only browser eval to return only the count of registered entries and the count whose apiKey is a nonempty string in JSON.parse(localStorage.getItem('openhands-backends') || '[]'). Expected: backend credentials are not readable by arbitrary same-origin script. Known failure, reproduced 2026-10-12: registered entries have readable plaintext keys (#16492). Retain counts/booleans only, never key values or whole storage JSON.

Manually registered same-origin key rotationF25.add-agent-server · F25.recovery-gateKnown failureKnown issue

Scenario F25--manually-registered-same-origin-key-rotation · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Reload the page.

Expect

Connect stays disabled until Host Name and a valid Host are filled; the API Key field is optional for Connect on Local and required on Remote, but a key-protected host still rejects an empty key. A dead host, an empty key or a wrong key shows Could not connect to <host> with the reason inline (Disconnected (check URL or network) for a dead host, Invalid API key for an empty or wrong key); a reachable host with the right key, entered on the default Local location, adds the backend, makes it active (BM-001) and redirects a /conversations/<id> page to /conversations without an error toast, also on the backend's first activation, without asking the new backend for the old conversation id.

reloading while the active backend is down shows only agent-server-onboarding-screen with Manage backends in recovery mode (no X, no Done, Escape ignored, primary Add Backend); choosing a healthy row restores the app.

Known bug observations

Known failure, reproduced 2026-10-12: default-local matches the injected key, the manually registered entry does not, and the UI shows Disconnected/Unauthorized (#17371). This version has visible status feedback, so do not repeat the older claim of a completely silent loop. Choose Connected Local to recover, then remove the owned duplicate.

Linked known issues

Known issue OpenHands/software-agent-sdk#5789 (F25.add-agent-server, F25.health-status): a deferred-init Agent Server can report ready after accepting a GUI origin through /api/init, while browser preflights from that origin still fail and Canvas cannot connect. Recheck an owned warm-pool server initialized with the actual non-localhost Canvas origin and compare admission with an origin supplied at server boot. The default loopback-origin stack is already allowed and cannot exercise this branch.

Exact CLI recipe & bug checks

Through Add Backend → Agent-server, add an owned entry named QA_Rotation_Legacy with the run's exact origin and fill its API Key using --value-file <run>/private/session-key; keep it active. Run restart --rotate-key, doctor, and browser reload. Compare only each saved entry's apiKey === window.__AGENT_CANVAS_SESSION_API_KEY__ boolean. Expected: same-origin entries accept the newly injected session key and the active backend recovers. Known failure, reproduced 2026-10-12: default-local matches the injected key, the manually registered entry does not, and the UI shows Disconnected/Unauthorized (#17371). This version has visible status feedback, so do not repeat the older claim of a completely silent loop. Choose Connected Local to recover, then remove the owned duplicate.

Non-loopback default backend nameF25.selector-dropdownKnown failure

Scenario F25--non-loopback-default-backend-name · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do — reading outline

  1. Do
    Serve the unchanged frontend on an owned non-loopback LAN address with --auth-required, no session-key injection and real protected backend routes. Open that origin in a new browser tab with --allow-external; its Add a backend step defaults Host Name to Local. Authenticate only with the private run key file, continue once and skip the remaining setup, then inspect backend-selector. Expected: the default name identifies the backend's host on a remote origin. Known failure, reproduced 2026-10-12: the selector reads Connected Local on the non-loopback origin (#16297). This is a LAN-origin check, not a VM deployment claim. Stop only the owned fixture frontend and return to the original tab.

Expect

clicking (or hovering) the selector opens a listbox of backends, each option named <status> <name> with a status dot, and a footer with Add Backend and Manage Backends.

Known bug observations

Known failure, reproduced 2026-10-12: the selector reads Connected Local on the non-loopback origin (#16297). This is a LAN-origin check, not a VM deployment claim. Stop only the owned fixture frontend and return to the original tab.

Linked known issues

Expected: Escape never clears the backend selector. Known failure (reproduced 2026-10-09, 2026-10-12, 2026-10-13 and 2026-10-14, #18233): Escape on the focused backend selector while its menu is closed (a second Escape after the one that closes the menu, and sometimes the Escape that closes Manage Backends opened from it) clears the selection: the closed selector shows the active backend's name as an italic placeholder (browser value 'testid=backend-selector >> role=combobox' is "") and its status dot is gone, so browser wait 'testid=backend-selector >> role=status[name="Disconnected"]' times out although the backend is down; a reload restores it. Reload before a closed-selector status check, as the Edit bullet does. Repro: browser click 'testid=backend-selector', browser press Escape twice (#18233). On 2026-10-12 bug proof verified fix PR #18238 at 77de0ad24 against main f07aebdf5 (two xfail, two xpass, desktop). control-openhands bug run F25.selector-dropdown replays it (desktop, after a reload with the mouse off the selector: a reload under a mouse left on it opens its menu and empties its field, which would look like the bug) and reports xfail while the bug lasts: sh known-bug F25.selector-dropdown escape-closed control-openhands browser viewport desktop control-openhands browser goto / control-openhands browser hover 'testid=root-layout' control-openhands browser reload control-openhands browser wait 'testid=backend-selector >> role=status[name="Connected"]' --timeout 15000 control-openhands assert --field value --equals Local -- browser value 'testid=backend-selector >> role=combobox' control-openhands browser click 'testid=backend-selector' control-openhands browser press Escape control-openhands browser press Escape control-openhands browser screenshot 'testid=backend-selector' --feature F25.selector-dropdown --name escape-closed control-openhands assert --field value --equals Local --bug-shows '' -- browser value 'testid=backend-selector >> role=combobox' # bug

Exact CLI recipe & bug checks

Serve the unchanged frontend on an owned non-loopback LAN address with --auth-required, no session-key injection and real protected backend routes. Open that origin in a new browser tab with --allow-external; its Add a backend step defaults Host Name to Local. Authenticate only with the private run key file, continue once and skip the remaining setup, then inspect backend-selector. Expected: the default name identifies the backend's host on a remote origin. Known failure, reproduced 2026-10-12: the selector reads Connected Local on the non-loopback origin (#16297). This is a LAN-origin check, not a VM deployment claim. Stop only the owned fixture frontend and return to the original tab.

Cloud log back inF25.cloud-log-back-inBehavior contract

Scenario F25--contract-f25-cloud-log-back-in · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do

This behavior has no separately named recipe in this snapshot. Use the family’s entry points and the contract below; the page does not invent a replay.

Expect

a logged-out Cloud row shows Logged out and a Log back in button that reruns the device flow.

Cloud org suspendedF25.cloud-org-suspendedKnown issueBlocked

Scenario F25--contract-f25-cloud-org-suspended · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do

This behavior has no separately named recipe in this snapshot. Use the family’s entry points and the contract below; the page does not invent a replay.

Expect

when a Cloud call made for the selected organization (X-Org-Id) fails with 403 and detail Organization is suspended or User membership is suspended, the app is replaced by <org> is suspended. Contact your administrator to restore access. (or Your access to <org> is suspended. Contact your administrator to restore it.), then Switch to another workspace and one button per other organization (Personal Workspace for the user's own). A button selects that organization and the app returns; with no other organization only the message shows. A reload clears the in-memory record. Blocked here: needs an OpenHands Enterprise instance with ENABLE_SUPER_ADMIN where a Super Admin suspended the organization or this member's membership, signed in as a non-Super-Admin member with that organization selected (#17988).

Linked known issues

Known issue OpenHands/automation#565 (F25.cloud-org-rows, F25.cloud-org-suspended): An org-scoped Cloud automation request with an unauthorized X-Org-Id can turn the identity service's 403 into a misleading 502/outage page. Recheck with an authenticated Cloud org-bound key and a disposable unauthorized organization id, comparing automation list and the identity response while preserving credential redaction. Local mode bypasses this identity service and cannot reproduce it.

Locked cloudF25.locked-cloudKnown issue

Scenario F25--contract-f25-locked-cloud · Permalink · Source

Given

Use F25’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do

This behavior has no separately named recipe in this snapshot. Use the family’s entry points and the contract below; the page does not invent a replay.

Expect

a Canvas served with --lock-to-cloud <url> shows only the Cloud login (no close) on first run, and its selector footer reads Reconnect to Cloud with no add, edit or remove.

Linked known issues

Known issue #18029 (F25.locked-cloud): A returning user in a locked Cloud deployment can reach the no-backend placeholder and fail to start conversations despite a healthy configured backend. The report proposes a stale document or alternate origin but does not capture which occurred. Recheck against the affected locked deployment with a Cloud session, its exact origin, cache response headers and injected host state; the normal local launcher does not reproduce this deployment condition.

Family notes, prerequisites for known issues & limits Known failureKnown issueBlocked
  • Export the second run as OH_VERIFY_RUN_2 and pass it with --run; never export it as OH_VERIFY_RUN, or the browser commands look for a browser that run does not have. OH_VERIFY_RUN= control-openhands launch --new ... keeps the launch from reusing this run.
  • The second backend asks its own telemetry consent the first time it becomes active, over whatever page you are on; onboard --skip answers it. Consent is stored per backend, so a later restart of the second run does not ask again.
  • browser fill <sel> --value-file FILE takes no positional value; it keeps the key out of the shell history and the evidence. Edit forms show the stored key masked, but browser snapshot of the Edit form prints it in clear: do not save that snapshot as evidence with a real key.
  • Manage-row test ids use the backend name (manage-backends-row-QA_Second), so they change after a rename and are not unique when two backends share a name; the Cloud Log back in id uses the backend id instead (manage-backends-login-<id>-login-button).
  • Selector options are named <status> <name> (Connected Local, Disconnected QA_Renamed): select them with role=option[name="Connected <name>"].
  • After a navigating option click, --expect-url must allow ?backend=...: end regexes with (\?|$).
  • browser wait-tab <regex> matches any open page, including the current one, unless --new leaves out the tab the daemon is on: for a second tab at the same URL (/conversations from New Chat) use wait-tab '/conversations$' --new, or read browser tabs and switch with browser tab <i>. browser tab new /conversations opens a plain second tab (no opener, empty sessionStorage), the state a user reaches by opening the app again. Close extra tabs highest index first, or the indexes shift under you.
  • browser tab new switches the daemon to the tab it opened, so a wait-tab <regex> --new run right after it reports the tab you came from (index 0 with two tabs); browser tab 0 first when you want the new tab's index from wait-tab. A tab new and a click --modifiers Control on New Chat reach the same state (selector from the localStorage fallback, sessionStorage item null).
  • A tab opened with click --modifiers Control starts with an empty sessionStorage, so it boots from the localStorage fallback (the backend last chosen in any tab) unless its URL carries ?backend=. Opening a ?backend= link writes that backend to localStorage too: a per-tab check that relies on "last choice" must run before opening pinned links.
  • The Add Backend Agent-server form has no inline "required" messages; it only disables Connect. The messages exist in the Edit form.
  • Footer clicks from the collapsed popover or the phone drawer open the modals a moment later: browser wait for them instead of count.
  • The device-flow popup survives errors: every failed attempt leaves an about:blank tab (#17953). Cancel closes it; after an error close it with browser close-tab.
  • With the default host the device flow talks to the real app.all-hands.dev (POST /oauth/device/authorize, then token polling). Starting and cancelling is harmless; approve a code only in the Cloud backend bullet, with an account you may use.
  • While the second stack's agent server is stopped or restarting, its ingress answers without CORS headers, so the browser logs Access to fetch at '<second-url>/api/settings' ... blocked by CORS policy (also /server_info, /api/conversations/search, /api/llm/models/verified). That is the dead upstream, not a CORS bug; a healthy second stack sends access-control-allow-origin for this origin.
  • Expected: Escape never clears the backend selector. Known failure (reproduced 2026-10-09, 2026-10-12, 2026-10-13 and 2026-10-14, #18233): Escape on the focused backend selector while its menu is closed (a second Escape after the one that closes the menu, and sometimes the Escape that closes Manage Backends opened from it) clears the selection: the closed selector shows the active backend's name as an italic placeholder (browser value 'testid=backend-selector >> role=combobox' is "") and its status dot is gone, so browser wait 'testid=backend-selector >> role=status[name="Disconnected"]' times out although the backend is down; a reload restores it. Reload before a closed-selector status check, as the Edit bullet does. Repro: browser click 'testid=backend-selector', browser press Escape twice (#18233). On 2026-10-12 bug proof verified fix PR #18238 at 77de0ad24 against main f07aebdf5 (two xfail, two xpass, desktop). control-openhands bug run F25.selector-dropdown replays it (desktop, after a reload with the mouse off the selector: a reload under a mouse left on it opens its menu and empties its field, which would look like the bug) and reports xfail while the bug lasts:

sh known-bug F25.selector-dropdown escape-closed

  control-openhands browser viewport desktop
  control-openhands browser goto /
  control-openhands browser hover 'testid=root-layout'
  control-openhands browser reload
  control-openhands browser wait 'testid=backend-selector >> role=status[name="Connected"]' --timeout 15000
  control-openhands assert --field value --equals Local -- browser value 'testid=backend-selector >> role=combobox'
  control-openhands browser click 'testid=backend-selector'
  control-openhands browser press Escape
  control-openhands browser press Escape
  control-openhands browser screenshot 'testid=backend-selector' --feature F25.selector-dropdown --name escape-closed
  control-openhands assert --field value --equals Local --bug-shows '' -- browser value 'testid=backend-selector >> role=combobox'  # bug
  • Local and remote backends are probed every 30 s (settings + server_info), Cloud backends every 5 min (#18128), so allow up to 40 s for a local dot to change. browser network --clear, wait about 70 s, then browser network --filter server_info shows two probes per local origin 30 s apart. While the active backend is down, any reload lands on the recovery gate: drive the disconnected rows on the page that was already loaded.
  • /oauth/device/verify is a Cloud page that also ships in Canvas: on a local stack its POST target does not exist, so Authorize always ends in Error. The "Authentication Required" state is unreachable (useIsAuthed always resolves true).
  • /shared/conversations/<id> on a local backend also raises a raw toast HTTP request failed (404 Not Found): {"detail":"Not Found"} next to Conversation not found (#17953).
  • Known failure (repro candidate, not an exemption): F25.edit-escape closes the Manage modal underneath and drops the Edit draft (Cancel and X close only Edit, F25.edit-cancel) (#17953).
  • The add bullet's network --external --filter 'ids=' check guards an earlier race: #18390 tracks the same first-activation symptom; on a backend's first activation from /conversations/<id>, about 1 attempt in 5 asked the new backend for the old conversation id. It did not happen in 15 first activations at 4eff80237: each time QA_Second was removed, its consent unset with control-openhands api PATCH /api/settings --run "$OH_VERIFY_RUN_2" --write --data '{"misc_settings_diff":{"app_preferences":{"user_consents_to_analytics":null}}}' (arrange only) and the backend added again from /conversations/<id>. A backend that lacks an id answers [null], which now reads as "not found" (only the This conversation does not exist… toast), not as incompatible data. No merged change was shown to fix the race itself (#18009 only changed how a [null] answer reads; #18024 only moved the redirect into a shared hook, still called after addBackend), so a hit (an ids= request to <second-url>) is a bug to file. At 95115e84f6dd it hit on 1 of 2 first activations from /conversations/<id> (one GET <second-url>/api/conversations?ids=<id> answered 200, with no This conversation does not exist… toast after it). The hit followed a failed Connect in the same modal (an empty key, Invalid API key) before the successful one; the clean retry, which connected once, had none, so a repro should try both sequences. On 2026-10-08 at d2c89252d it did not reproduce in 17 first activations (8 clean, 9 failed-then-clean, some under CPU load and some human-paced): every ids= capture was empty, and no merged change touched the activation logic since the hit, so it stays a watch item rather than a filed defect. Record the Add bullet as fail when the request appears, even though the redirect and the selector look right. Known failure (reproduced 2026-10-13, #18390): two independent first activations hit it that day, each the run's first activation of QA_Second after a clean Connect. On 2026-10-13 at 2bdc0e9d6 it hit on 1 of 6 first activations from /conversations/<id>: the first one of the run (GET <second-url>/api/conversations?ids=<id> 200, no toast), a clean Connect in a modal opened after earlier failed Connects in other Add Backend openings; the next 5 (2 failed-then-clean, 3 clean, plus one Manage → Add) had none.
  • Add Backend and Manage Backends take and keep keyboard focus, and Edit backend takes it on open (ModalBackdrop; at phone width open the selector with 'testid=sidebar-mobile-drawer >> testid=backend-selector', since the bare test id matches two selectors). At desktop width focus returns to the selector input when they close (at phone width Add Backend's X closes the drawer too and focus falls to the page body), so an Escape pressed after closing lands on the selector and can trigger #18233 below.
  • Toasts are not page or HTTP errors, so browser errors --app-only misses them: read browser toasts --history after every add or switch, before anything reloads the page (the history starts with each page load).
  • F25.locked-cloud needs scripts/static-server.mjs --lock-to-cloud <url>, which neither bin/agent-canvas.mjs nor control-openhands launch can pass today; even with it, reconnecting needs a Cloud account.
  • The Cloud tab title truncates to OpenHan… at 390 px when the Agent-server tab is selected; the layout still fits. With the Providers tab present all three titles are cut to three letters.
  • A token DigitalOcean rejects surfaces as the bare 401 Unauthorized, with no hint that the token is wrong or lacks scopes.
  • Known issue #18029 (F25.locked-cloud): A returning user in a locked Cloud deployment can reach the no-backend placeholder and fail to start conversations despite a healthy configured backend. The report proposes a stale document or alternate origin but does not capture which occurred. Recheck against the affected locked deployment with a Cloud session, its exact origin, cache response headers and injected host state; the normal local launcher does not reproduce this deployment condition.
  • Known issue #17390 (F25.cloud-sandbox-states): Every sent Cloud message can fail with token_not_found_in_db from the model proxy. Recheck with the affected Cloud account/session and a minimal no-tools prompt; capture the error type with provider token text and hashes redacted. The report supplies no deployment version or deterministic token-provisioning sequence, and this run has no Cloud account.
  • Known issue #17163 (F25.cloud-sandbox-states): A Free Cloud account can lose access to previously usable models with HTTP 403 and a team-restriction message despite no team being created. Recheck with the affected account and its model entitlement/org history; local credentials cannot prove Cloud eligibility and this run has no such account.
  • Known issue #16874 (F25.cloud-sandbox-states): A failed resume of a paused Cloud sandbox was reported to consume a one-shot guard and leave chat disconnected until remount. Current source uses a retrying resume hook, so recheck the original failure and a second pause in one route mount before concluding resolution. Blocked without a real paused Cloud sandbox and a controlled one-request backend/network failure; source changes alone are not a live pass.
  • Known issue #15676 (F25.cloud-sandbox-states): Refreshing an approximately 24-hour-old Cloud conversation on Vercel can fail to resume/reconnect. Recheck an authenticated affected Cloud deployment with an aged sandbox/session and exact console/network error; the report omits its commit and failure text, and no aged Cloud fixture is available here.
  • Known issue #15822 (F25.switch-backend F25.health-status F25.cloud-org-rows): Cloud-proxy traffic was reported to continue after switching from Cloud to Local. Current source probes registered inactive Cloud backends on a five-minute cadence; recheck the reported repeated OPTIONS behavior with a real registered Cloud account/org and capture only credential-redacted request metadata after switching. This run has no authenticated Cloud backend.
  • Known issue OpenHands/automation#565 (F25.cloud-org-rows, F25.cloud-org-suspended): An org-scoped Cloud automation request with an unauthorized X-Org-Id can turn the identity service's 403 into a misleading 502/outage page. Recheck with an authenticated Cloud org-bound key and a disposable unauthorized organization id, comparing automation list and the identity response while preserving credential redaction. Local mode bypasses this identity service and cannot reproduce it.
  • Known issue OpenHands/software-agent-sdk#5789 (F25.add-agent-server, F25.health-status): a deferred-init Agent Server can report ready after accepting a GUI origin through /api/init, while browser preflights from that origin still fail and Canvas cannot connect. Recheck an owned warm-pool server initialized with the actual non-localhost Canvas origin and compare admission with an origin supplied at server boot. The default loopback-origin stack is already allowed and cannot exercise this branch.

Before you start

Use an OpenHands checkout at the source revision, with Node 24+, installed npm dependencies, uv / uvx, and a supported Chromium browser. The verification skill is the complete setup contract.

export PATH="$PWD/.agents/skills/verify-openhands/scripts:$PATH"
export OH_VERIFY_RUN=$(control-openhands launch --new --print-run)
control-openhands doctor
control-openhands onboard --skip
# Configure a model only for a scenario that needs one:
control-openhands llm preset deepseek
# When the run is complete:
control-openhands stop

F01 walks onboarding instead of skipping it. No-model checks run before model configuration. Keep OH_VERIFY_RUN set so commands address your own run. Desktop is 1440 × 1000; phone is 390 × 844. Model-backed checks need the configured provider key and budget.

Recipes run in source order within each family and can share fixtures. The page preserves that order; “as above” is a dependency on an earlier case. Setup commands and API writes arrange state. They do not prove a UI path. Follow the family cleanup steps.

A known failure preserves the intended expectation beside the observed defect. Executable known-bug blocks use xfail for a reproduced symptom and xpass when it stops reproducing. Cloud accounts, enterprise capabilities and other unavailable prerequisites remain explicit. “Not run” is different from “blocked”.

The maintained map describes application behavior at its pinned revision. Once a fix is verified in the application version under test, remove the obsolete failure note and keep the normal expectation. Dated evidence belongs in the backfill ledger.

Read the full map index and coverage boundaries, the CLI guide, or the earlier illustrated edition. The verification approach adapts Lauren Tan’s (@poteto) pstack verification-skill generators; attribution and adaptation.