Engel’s notebook

OpenHands,
one scenario at a time.

The OpenHands app map in the Self-Healing Codebase Project. Named mini scripts show what to set up, what to do, and what should happen.

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

How to read the scripts & bug markers
Given
The starting state and prerequisites.
Do
Actions, followed by observations.
Expect
The intended application behavior.

Open a scenario to read its script. The reading outline highlights the action path; Expect gives the behavior to check. Open Exact CLI recipe & bug checks for every instruction, diagnostic, selector, placeholder and executable known-bug block. Use that original recipe for a complete replay.

Known failure A defect reproduced in the source map.

Known issueBlocked A reported issue or path awaiting its named prerequisites. A blocked check is not a reproduced failure.

Not run A check not exercised in the source map.

Mapped behavior An expected behavior; this marker does not mean it passed.

Markers describe the particular paths and notes inside a script. A script can exercise a local path and also record an issue blocked on Cloud prerequisites.

F01 First run, onboarding and sign-in

22 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Use a fresh run or browser reset for every first-run check, and never onboard --skip first. The browser profile lives in <run>/private/browser-profile. browser reset clears only the browser, so the server keeps the consent answer (the consent modal does not return), the LLM profile and the agent kind.
  • control-openhands login passes both key prompts: the onboarding backend step, and the API-key screen, where it names the backend Local when Host Name is empty (after Skip and after restart --rotate-key, with no duplicate backend). Use it when the screen is only in the way. To check the screen itself (the disabled Connect, the wrong-key line, a chosen name), use the explicit fills above.
  • Inactive onboarding slides stay mounted and are translated off-screen. Plain browser testids lists only the active slide, but count and wait still find handles on the others. Assert the active step with data-current-step, for example browser wait '[data-testid=onboarding-modal][data-current-step="2"]'. The testid= selector cannot carry attribute filters.
  • Slide indices renumber. After the public backend step succeeds, "Choose your agent" becomes index 0 and has no Back. previewOnboardingStep counts phases with the backend step included, so 0 and 1 look identical on a healthy backend.
  • The consent modal (z-70) sits over the onboarding modal, and clicks at the screen center land on agent tiles once it closes. A forced click on first-run-onboarding-screen selected Codex. Check backdrop non-dismissal with browser mouse-click 40 40, a corner outside the modal.
  • The say-hello default message ("Create a basic webpage…") makes the agent work for a while. Replace it with a tiny prompt to keep model cost down.
  • On a local backend, Next on the LLM step also points the default agent profile at the profile it just created. Activating another LLM profile does not move that pointer, and while it stays, deleting the onboarding profile answers 409 LLM profile is referenced by 1 agent profile(s): default. control-openhands llm preset deepseek moves it to deepseek-flash and reports repointed; llm set does not. In the UI, edit default in Settings → Agents and pick the profile in testid=agent-profile-llm-selector.
  • On a local backend the LLM step edits an LLM profile and never reads or writes the raw LLM settings (#18218). It takes the active profile, else the one the default agent profile points at (the Agent Server's backfill of an older install's raw settings), else a fresh one with the default model. It reads the profile only once the backend's health check reports it connected, so a step mounted while the Agent Server was down fills in after it comes back (verified 2026-10-12 with control-openhands service stop agent-server, then restart). If the profiles cannot be read it shows Failed to load existing profile. Please try again. (testid=onboarding-llm-load-error) with Retry (testid=onboarding-llm-load-retry) and Next disabled. Next without changes only sends POST /api/profiles/<name>/activate.
  • The tab title prefixes a status emoji to the stored title. A generated title that already starts with an emoji therefore shows twice (✅ ✅ Reply hello without tools | OpenHands). Right after launch the title is Conversation <id prefix> until a reload picks up the generated one.
  • Right after Next, the ACP step reads "Checking for an existing Claude Code login…" with Next disabled. Wait for testid=onboarding-acp-auth-detected before reading the banner. The "already signed in" banner depends on the host's CLI login. On a clean CI machine the fields become required, and Next is blocked until they are filled.
  • Choosing an ACP agent saves agent_kind: acp immediately. Re-choose OpenHands before leaving, or later conversations in the run use the ACP agent.
  • In preview mode, Skip and Close are inert. The LLM slide shows gpt-5.6-sol even when another model is saved, so do not read it as data loss.
  • browser toasts --history lists every status or alert text (toasts and alert banners) seen since the page loaded, including success toasts and progress lines such as Checking your backend connection…. Read it on a page that has just loaded (or reloaded) to check that no error toast appeared. A page with a standing banner never reads []: the app shell of a backend with no LLM lists the Your LLM isn't set up yet alert.
  • Known issue #16601 (F01.telemetry-consent): Analytics consent reappears sporadically. Live prerequisite: The multi-day trigger and backend consent response when the modal returns; opted-in localStorage alone does not reproduce it.
  • Known issue #16526 (F01.onboarding-choose-agent): 403 error when selecting an agent in the onboarding agent selection modal. Live prerequisite: An authenticated hosted Cloud account with the reported organization-membership mismatch during onboarding.

F02 App shell, sidebar and command menu

28 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • The rail animates its width for 200 ms. A bbox taken right after the collapse click reads an intermediate width (about 70); measure after browser reload.
  • browser url straight after clicking Customize can still read /customize; desktop redirects to /mcp a moment later. Use browser wait-url '/mcp$'.
  • /settings redirects to /settings/agents on desktop but shows the settings menu on phone. That redirect also marks the checklist's Customize your agent item done, so visiting Settings by any route completes it. The sidebar Customize link (/customize) does not.
  • The pin toggles are hidden until hover (visible is false), except a pinned row's toggle, which stays visible. They exist only on the expanded rail.
  • The two desktop elements share the label Sidebar navigation, and settings pages add a second complementary landmark, so role=complementary is ambiguous. Use aside[data-collapsed] for the desktop rail and testid=sidebar-mobile-drawer for the drawer.
  • HeroUI tooltips (the collapsed rail and checklist previews) often stay closed on the first browser hover after a navigation. Hover a neighbor first, then the target, and browser wait for the tooltip. Never count it right away.
  • When collapsed, the expand chevron is opacity-0 pointer-events-none until the rail is hovered. A forced click on it lands on the logo link and does nothing; hover the logo first.
  • With the full list open, the command-menu panel covers the viewport center, so clicking the backdrop button by selector only works after a filter shortens the list (type secr first). control-openhands browser mouse-click 1300 500 hits the desktop backdrop with any list length.
  • The new-tab path works with browser click ... --button middle and with --modifiers Control (or Meta). The phone drawer's backdrop has no handle; browser mouse-click 350 400 clicks it.
  • Escape closes one layer at a time (#18044): with the command menu open over the phone drawer, the first Escape closes the menu and the second closes the drawer. Focus goes back to whatever had it when the menu opened: right after browser goto /conversations that is the composer (chat-input takes focus on load), and on /settings/app it is <body>.
  • Toggle sidebar at phone width flips the stored desktop state without any visible change. Expand the rail again after returning to desktop.
  • Typing does not reset the command menu's selection. The active option keeps its index, cut down to the shorter list, from the last ArrowUp/ArrowDown or hover, and a pointer resting where the list opens hovers the option under it. A term with several matches can then run a later one on Enter: after two ArrowDown presses, typing LLM profiles (LLM profiles and Model Router) selects Model Router. Before opening the menu for an Enter check, rest the pointer with browser hover 'testid=command-menu-trigger', and read aria-activedescendant before Enter when a term matches several items. Repro candidate, not yet filed.
  • The command menu's LLM profiles item uses a magnifier icon (lucide-search, the same icon as the menu's search field).
  • Dismissing the checklist with its X and the App settings switch write the same browser-local flag, so either one undoes the other. Clicking Join the OpenHands Slack counts as joining and opens an external tab you must close.
  • Registry access depends on the sandbox. Where the browser cannot reach registry.npmjs.org (ERR_CERT_AUTHORITY_INVALID), the update check reports Couldn't check for updates; that is the environment, not the product. Where it can, the badge reads Up to date and the check says You're running the latest version.
  • Adding a custom MCP server tests the connection first and refuses an unreachable one (Could not reach the server..., or Connection failed: McpError: Connection closed for uvx mcp-server-time here), so the all-complete recipe uses the local stdio fixture.

F03 Home and starting work

42 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Once a workspace is confirmed, open-workspace-button is not rendered (the preview bar replaces it): a click on it waits the full 30 s. Reopen through home-git-control-bar-preview, or reload.
  • A pick in the workspace dropdown is stored in sessionStorage at once, before Confirm or Launch, and closing the dialog with the X keeps it: a bullet that needs the earlier workspace selected must pick it again instead of relying on the last confirmation.
  • control-openhands workspace open and conversation start --workspace save that folder as a standalone workspace (api GET /api/workspaces lists it under workspaces). When another family used them first on the same stack, Add all subdirectories leaves those folders out of the workspace parent and the dropdown is grouped: options read workspace, qa-f03-repo and Other, qa-f18-repo. role=option[name="qa-f03-repo"] still matches (substring), but the menu text and the workspace-group-header count 2 in the Search bullet assume F03's own fixtures only.
  • Folder rows navigate on a single click; there is no path field. The browser starts at the server home, <run>/private/home, two levels below <run>. After any conversation without a workspace, <run>/private/home/workspace exists: it shows as a workspace row and, after the next page load, a workspace favorite (folder-browser-sidebar-workspace), which is not <run>/workspace.
  • A workspace and its parent can share a name (workspace); their test ids differ only by prefix (manage-workspaces-row- vs manage-workspaces-parent-row-).
  • chat-input is a contenteditable div: it stays editable when the composer is "disabled"; assert the Send button and the absence of a new conversation instead.
  • conversation start without --stay navigates home and loses a picked workspace and plugins; drive the submit button as above.
  • Responder Continue with local setup writes an OPENHANDS_URL secret; delete it so F14 counts stay clean.
  • The rail lists about 15 cards (proven ones, then conversation launchers such as research-brief-writer); browser testids reports only the first few, so check a card with browser count 'testid=recommended-automation-rail-card-<id>'. Any automation whose name matches a card (Daily news digest) hides that card, so avoid catalog names in fixtures.
  • The home draft (oh:home-prompt-draft) survives the no-LLM bullets; browser type appends to it, so browser fill 'testid=chat-input' '' before typing a slash command.
  • Imported automations arrive disabled and home lists only enabled ones; turn them on before looking for rows. A run dispatched from home and then cancelled still leaves a QA_F03 Pong — <date> conversation in the sidebar.
  • With the Agent Server stopped, a reload does not render home at all; trigger the error path before reloading, then restart.
  • Deleting the last automation leaves its id in the pinned localStorage key (pruning skips an empty list); the grid still hides it.
  • Pinned cards reorder only by mouse drag; there is no keyboard or menu alternative. Drive it with browser drag <handle> --by DX,DY --steps 20; browser drag <handle> <target> reports ok but leaves the order unchanged.
  • VITE_HOME_AUTOMATIONS_DEMO=true (build-time) swaps the home automation sections for demo data; never verify against such a build.
  • The Code / Automate choice is component state, not stored: every page load and every return to home starts in Code. A toggle check after a navigation must click Automate again.
  • When the Agent Server is stopped, background refetches can add a generic An error occurred toast (#18051). For F03.create-error-toast, retain the complete video and toast history and report any extra toast with its timing; do not waive it from the count.
  • The workspace combobox uses one message for an empty list and for a search with no match: both read No workspaces yet (workspace-dropdown-empty), with + Add Workspace still below it (#18037).
  • Known issue #17108 (F03.open-repository-dialog): The repository picker lists a search result for input the user has already cleared. Live prerequisite: An authenticated Cloud Git provider and a real repository search response that remains pending after clearing the input.
  • Known issue #16801 (F03.launch-without-workspace): Agent starts by looking in workspace, even when started with "no workspace". Live prerequisite: A working Claude Opus agent and the original task/prompt that makes it explore the default workspace; the prompt is absent.
  • Known issue #16210 (F03.plugin-picker): [Cloud Backends] Plugins not available when starting a new conversation. Live prerequisite: An authenticated Cloud organization with configured account plugins before opening New Chat > Plugins.
  • Known issue #15701 (F03.launch-in-workspace): Creating Conversation toast stuck visible. Live prerequisite: The original stuck creation request/outcome sequence; the report is intermittent and gives no deterministic trigger.
  • Known issue #18480 (F03.open-repository-dialog): an Azure DevOps Enterprise user belonging to several organizations can see only one organization's repositories. Live prerequisite: a real Cloud/Enterprise sign-in with two organizations and distinct repositories; the report's mocked two-organization capture is not a real-backend reproduction.
  • Known issue #18418 (F03.create-error-toast, F03.launch-with-plugin): hosted runtime 1.50.1 can fail during skills/plugins setup because its packaged plugin-schema resource is missing. Live prerequisite: the affected Cloud runtime/PyInstaller image and original plugin setup plus its account. A Local 1.55.0 launch does not verify that deployment.
  • Known issue OpenHands/software-agent-sdk#5837 (F03.folder-browser): case-only directory names at a page boundary can repeat the server cursor; Canvas detects the repeated cursor and the directory listing fails instead of showing all choices. Live prerequisite: a case-sensitive filesystem with enough directories to cross the real picker page boundary, including two distinct case-only names. A case-insensitive Mac volume cannot hold that fixture.
  • Known issue OpenHands/software-agent-sdk#5849 (F03.folder-browser, F03.manage-workspaces): re-adding a saved workspace or removing an already-removed stale row can return500 because the registry writes even for a no-op. Live prerequisite: an owned read-only workspace registry plus the already-saved or stale picker row; changes to registry mtime/inode alone are not a Canvas failure.

F04 Conversation list and folders

41 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • A plain --expect-url '/conversations/' returns at once when the browser is already on a conversation page. Start creation clicks from / (browser goto /), and never navigate away before the click returns: leaving early skips the client-side metadata write, so the new conversation lands in No workspace although the server has its working directory.
  • Workspace grouping, pins, archives and preferences live in this browser's localStorage (openhands-agent-server-conversation-metadata, pinned-conversations, archived-conversations, conversation-panel-preferences). browser reset loses them: archived rows reappear and every conversation moves to No workspace.
  • Folder test ids embed the sanitized absolute workspace path (thread-folder-ws--tmp-...-qa-f04-repo). Select with [data-testid^="thread-folder-ws-"][data-testid$="<folder>"] or by the + button's accessible name.
  • Row links carry ?backend=default-local; anchor URL regexes without $.
  • Hover and ⋮ need the row on screen: browser scroll '<row>' first, and browser hover before clicking ellipsis-button or the pin.
  • Escape closes the ⋮ menu and returns focus to its ⋮ button (#18044); an outside browser mouse-click closes it too. If Tab has moved focus to the next row's link, that row's preview tooltip opens and takes the first Escape, so press Escape twice. Inside the phone drawer the first Escape closes the menu and the next one the drawer.
  • The Stop, Delete and Delete all dialogs name their buttons cancel-button and confirm-button, like the Archive and Edit tags dialogs (#17915, fixed by #18151); role=dialog >> role=button[name="..."] still works.
  • With the drawer open at phone width there are two conversation-panel elements; scope with testid=sidebar-mobile-drawer >> ....
  • Recent is exactly the fresh default layout (By date, Updated, all threads, old conversations shown), so a new user sees Recent checked. Active and Compact also turn on Hide old conversations: Recent plus a manual Threads → Active only reads More options · Custom, not Active. Presets never change the hide-older cutoff.
  • In By workspace mode Load more often adds no visible row: later rows join existing folders behind their More button.
  • By workspace never shows No conversations found while any workspace is registered: empty workspace folders stay.
  • Pinned rows bypass tag and automation filters and suppress the filtered empty states; unpin before asserting them.
  • The page size is 20; filters, Active scope and archives apply only to loaded pages.
  • The ⋮ menu offers Stop for idle and finished conversations too (they count as active); it disappears only once paused.
  • service stop agent-server needs control-openhands restart and a browser reload before anything else works.
  • The list refreshes on an interval: a row keeps conversation-status-working and Conversation <id5> for up to about 40 s after the conversation finished. Use browser wait ... --timeout 60000, not an immediate count.
  • Model-written titles vary from run to run; match finished conversations on a word from the prompt (has-text=pong), never on the whole title.
  • Opening a conversation from the sidebar never changes its tags, also right after an automation setup conversation (one tagged automationsetup: draft): the other conversation keeps its tags and opens as a normal chat (recipe in F07 F07.open-by-url). A conversation that did get the tag loses it with Edit tags (remove-tag-automationsetup).
  • Known issue #17919: Delete all deletes only the loaded pages, and its dialog states that smaller count (F04.delete-all).
  • Known issue #18155 (F04.show-archived-unarchive): Missing-runtime conversations shown as Archived but never hidden, chipped, or unarchivable. Live prerequisite: A Cloud sandbox or local runtime that becomes MISSING and non-resumable; ordinary local pause remains resumable.
  • Known issue #17497 (F04.card): Corrupted conversation data breaks the entire conversation history list instead of handling individual broken conversations. Live prerequisite: The original corrupted persisted conversation or a deterministic corruption fixture after model-switch failure and fork.
  • Known issue #17257 (F04.start-task-cards): Cloud provisioning has no in-progress start-task cards because the list hook always returns an empty array. Live prerequisite: An authenticated Cloud backend producing an in-progress sandbox/repository start task.
  • Known issue #16750 (F04.card): Conversation list does not load while automation is running. Live prerequisite: A prompt automation or native delegated task that remains running during conversation-list reload; no usable model is configured here.
  • Known issue #15946 (F04.card): Show conversations hidden by active/stale agent-server leases. Live prerequisite: A copied lease-bearing persisted conversation and second owner/expired-lease state; never alter another live run's state.
  • Known issue OpenHands/software-agent-sdk#4987 (F04.card): Autotitle input has broken user-message separators and emoji-list formatting; verify the resulting visible title with a real provider. Live prerequisite: A working autotitle model and captured title-generation input/output; Canvas only consumes the resulting server title.
  • Known issue OpenHands/software-agent-sdk#4537 (F04.card): A native delegated task can freeze the sidebar conversation list and cause request timeouts until it ends. Live prerequisite: A working model with native TaskTool delegation holding the parent state lock during list reload.
  • Known issue OpenHands/software-agent-sdk#4080 (F04.card): One unregistered persisted tool observation can make a whole conversation disappear from the list and return404. Live prerequisite: A copied persisted conversation with its original unregistered observation kind and missing tool-module registration.

F05 Composer, slash commands and plan mode

25 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Always export your own OH_VERIFY_RUN. launch --new repoints the shared current symlink, and another agent relying on current drove and then stopped this family's stack mid-run. If the URL jumps to a page you never opened or fetch failed appears, run control-openhands status before blaming the product.
  • conversation wait <id> right after sending returns at once with the previous finished status. Use conversation wait <id> --fresh; --until running then a terminal wait only works for turns longer than the poll interval (a Reply with only turn on deepseek-flash can finish first, and --until running then times out).
  • The slash menu stays open while you type a command; press Escape before Enter or Enter selects the highlighted option instead of sending.
  • Conversation drafts are saved 500 ms after the last keystroke and flushed on navigation and unmount, so text typed just before leaving comes back.
  • Escape closes the + tools menu (browser count 'testid=tools-context-menu' is 0) and returns focus to chat-plus-button, whether focus was on the trigger or on a menu item. A menu left open covers attachments in screenshots. Escape also closes the narrow-width "More input actions" overflow menu (browser count 'testid=chat-input-overflow-menu' is 0, the trigger's aria-expanded is false) and returns focus to the trigger, whether focus was on the trigger, on overflow-model-button or on a profile row of the open Model submenu (#18174).
  • After a client-side switch between conversations (a sidebar row, or a user-message branch in F06.branch-from-here) Send keeps the previous conversation's state (Known failure, #18202; draft fix PR #18203). Switching from an empty composer to one with a saved draft restores the text next to a disabled Send; this is a race (Send came up enabled in 1 of 7 tries), so one enabled reading does not show the bug is gone. Switching from a composer with text to an empty one leaves Send enabled, and clicking it does nothing. Typing or browser reload fixes it, and Enter in the field sends the restored text anyway. Read browser enabled 'testid=submit-button' after a reload, or after typing.
  • Expected: a prompt that a suggestion chip (testid=chat-suggestions) or a + > Macros item writes into the composer enables Send at once. Known failure (reproduced 2026-10-09, #18202): on a new No workspace conversation, browser click 'testid=chat-suggestions >> text=Fix README' (or browser click 'testid=chat-plus-button', browser hover 'testid=macros-button', browser click 'testid=fix-readme-button') fills the field, but browser enabled 'testid=submit-button' is false (5 of 5 tries at desktop; a race at phone) until a character is typed. Both paths call setMessageToSend, which writes the text without an input event.
  • Uploaded chips and thumbnails have no test ids. Their remove buttons are named Remove <file name> (file chip) and Remove image (thumbnail), and Send, Stop and Play read Send message, Stop and Resume the agent task (#18033); select by those names, or by text/alt.
  • browser upload feeds the hidden input directly. To drive the real path, + > Add Files and Images, use control-openhands browser upload-via 'testid=add-files-and-images-button' <file>, which answers the native file chooser.
  • The home composer has no interactive-chat-box wrapper: scope its chips and thumbnails with testid=home-chat-launcher.
  • conversation events rows show a message's text and its attachment count (images: 1 for the image-only message, whose text is a blank); --grep 'data:image/png;base64' searches whole events and quotes the image content block in excerpts.
  • A user row with images: 1 proves that the image reached the Agent Server, not the model. Only the reply proves that the model saw it. On agent-server 1.53.0 and older, deepseek/* profiles drop the image before the LLM call (F05.image-only-send). An image sent with no text then leaves deepseek-flash with an empty task. On 2026-10-08 it ran env, read the run directory and other files on the host, and decoded the picture from the event store with PIL to answer. Pause such a conversation (control-openhands conversation pause <id>), and use the question-plus-image send for the model check.
  • The pill resolves the conversation's stamped profile before the user-level default, so profiles that share a model are told apart by name. The stamp lives in this browser's localStorage (openhands-agent-server-conversation-metadata), not on the server (api GET /api/conversations/<id> has no profile name): read it with browser eval, not api GET.
  • The Switched to profile notes are client-side and gone after reload; assert the pill and conversation status.
  • /plan messages go to a separate planner conversation; the browser stays on the parent /conversations/<id>, so wait on the planner id from conversation list.
  • Only the newest goal status offers Resume (#18036): once a resumed goal has posted a newer status, the old interrupted row shows no button, also after a reload. Count testid=goal-resume after the resumed goal ends; while it runs the count is 0 anyway.
  • Opening the context popover logs a 404 for /api/llm/balance on the local backend in browser errors --app-only; it does not break the popover.
  • --observe takes the verbs' selector syntax: testid= and plain CSS segments are watched by an in-page MutationObserver (observedBy mutation), while role=, text= and other engines are polled every 20 ms (poll-20ms), which misses a state shorter than that; prefer a testid= or CSS form for anything brief.
  • At narrow (320 px) the overflow menu's Model submenu opens to the right of the menu and runs off screen: each profile row starts at x about 270 with width 210 (browser bbox 'testid=chat-input-llm-profile-option-deepseek-flash', insideViewport false), so both names read deepse…. Pick profiles by test id: the click lands on the strip of about 50 px that stays on screen. Known issue: #18063 (filed from the follow-up noted in #18015; the submenu also runs past the bottom edge, so the LLM Profiles link cannot be clicked at narrow) (F05.overflow-menu).
  • Known issue #17788 (F05.attach-files): Cloud attachment upload rejects valid runtime data when cached fields are empty strings. Live prerequisite: A Cloud sandbox whose cached runtime URL/key are blank while the fresh runtime lookup is already ready.
  • Known issue #17621 (F05.context-window-meter): Compact context always fails after exactly 60s — client-side timeout aborts a condensation the server then completes. Live prerequisite: A real summarizing model and conversation whose condensation takes longer than 60 seconds.
  • Known issue #17476 (F05.context-window-meter): Planning socket stats overwrite the main conversation's context meter. Live prerequisite: A real planning sub-conversation and main conversation with distinct nonzero usage stats.
  • Known issue #17429 (F05.slash-btw): 500 error with /btw. Live prerequisite: The exact pending tool/streaming state that triggers the generator-pickle error, plus a working model for the side question.
  • Known issue #17271 (F05.context-window-meter): New chat retains context/state from the previous conversation. Live prerequisite: A completed conversation with nonzero token usage before New Chat; a dummy-key run supplies no genuine context usage.
  • Known issue #17263 (F05.context-window-meter): Context meter stays pinned after a successful compaction once the condenser has its own usage entry. Live prerequisite: A real successful compaction whose condenser usage remains larger than the default agent usage.
  • Known issue #16851 (F05.profile-identity): wrong model is displayed when loading existing conversation. Live prerequisite: The original conversation/model-switch history and usage record showing Opus with a Fable selector; a dummy-key conversation has no usage evidence.
  • Known issue #16835 (F05.context-window-meter): Agent Canvas sidebar shows "Cache Hit: 0" while backend correctly records nonzero cache_read_tokens. Live prerequisite: A working prompt-caching model/proxy and a real conversation reporting nonzero cache_read_tokens.
  • Known issue #16430 (F05.attach-files): File upload does not have an indication of progress. Live prerequisite: A remote or throttled real upload that stays in flight long enough to inspect progress and composer state.
  • Known issue #16339 (F05.attach-files): Agent not informed of file location for files uploaded via canvas in cloud. Live prerequisite: An authenticated Cloud upload sandbox and working model to inspect the delivered filename/path notification and read the uploaded file.
  • Known issue #15782 (F05.draft-persistence): Prompt disappears when a conversation is stopped. Live prerequisite: Clarify whether prompt means unsent composer text or the already-sent history message, and capture that state before sidebar Stop.
  • Known issue OpenHands/software-agent-sdk#5639 (F05.llm-profile-picker): A profile switched before the first message can be absent from token/cost statistics after its successful turn. Live prerequisite: A working model, a profile switch before the first run, and nonzero usage/cost after that turn.
  • Known issue OpenHands/software-agent-sdk#5627 (F05.slash-goal): After backend restart the goal chip can remain Running while the conversation is paused, and Stop does not clear it. Live prerequisite: A working model goal loop active during graceful backend restart, then goal-status and resume readback.
  • Known issue OpenHands/software-agent-sdk#5523 (F05.image-only-send): DeepSeek V4.1 Flash images can be sent to another fallback profile because its vision capability is not detected. Live prerequisite: A working DeepSeek V4.1 Flash vision profile and another fallback profile, with an image-bearing turn.
  • Known issue OpenHands/software-agent-sdk#5492 (F05.slash-goal): A goal can stop as Complete when its judge replies with complete as the string false. Live prerequisite: A real goal judge response containing complete as the string false rather than a JSON boolean.
  • Known issue OpenHands/software-agent-sdk#5330 (F05.slash-btw): A /btw side question can prevent reuse of the normal conversation prompt cache. Live prerequisite: A working caching provider and comparable ordinary-message versus /btw prompt/cache records.
  • Known issue OpenHands/software-agent-sdk#4498 (F05.context-window-meter): Compaction can leave an orphaned tool call/result in the live history and make the next provider request fail. Live prerequisite: A real condensation splitting an action/result pair, followed by the next provider request.
  • Known issue OpenHands/software-agent-sdk#4382 (F05.context-window-meter): ACP cost can miss cache/thought usage, silently become zero, or inflate after stalled/reset cost updates. Live prerequisite: An authenticated ACP provider emitting cache/thought token usage or reset/stalled cumulative cost updates.
  • Known issue #17239 (F05.llm-profile-picker): The reported model/profile picker can open above the viewport with unreachable rows. A frozen-target Local check with eight LLM profiles passed on Home at 1512x720, 1440x1000, 390x844 and 320x700, and in a genuine conversation at 1512x720 and 390x844: internal scrolling and selecting the first/last profile worked. The Cloud screenshot and real ACP multi-model picker remain unverified; they need an approved Cloud session or ACP account/adapter with a multi-row model catalogue. Local profile geometry is not proof of those branches. Run backfill-18309-settings-worker, Agent Server/SDK 1.54.0; evidence includes F05.llm-profile-picker/17239-conversation-1512x720.png and 17239-conversation-phone.png.
  • Known issue OpenHands/software-agent-sdk#5824 (F05.slash-goal): resuming after a failed judge audit can show iteration 2 of a maximum of 1 and run beyond the selected goal cap. Live prerequisite: an agent/judge run whose audit fails, followed by a successful incomplete judge verdict after Resume; a controller-only test does not establish the Canvas banner outcome.
  • Known issue OpenHands/software-agent-sdk#5889 (F05.attach-files): a same-name attachment upload that fails part-way can replace the previous workspace file with partial content. Live prerequisite: an owned isolated backend with a controlled mid-upload filesystem failure (the original Linux file-size limit or an equivalent bounded child-process limit), a pre-existing target and a real Canvas overwrite. Never change resource limits on a shared runtime; a normal successful upload does not verify failure atomicity.
  • Known issue OpenHands/software-agent-sdk#5820 (F05.context-window-meter): manual compaction on a null/no-op condenser can return500. Verification is blocked by genuine populated usage/context metrics: idle and refused-loopback runs leave usage=null, so neither composer nor Usage panel offers Compact context. A separate direct endpoint check returned500 but is not a Canvas reproduction. Use an existing real conversation with usage and no condenser, or an authorized successful provider run; never fabricate stats to expose the action.

F06 Agent activity: messages, events and lifecycle

35 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • control-openhands conversation events <id> returns the last 25 events unless you pass --last N; it reads the server's 100-event pages until N rows are in hand (pages says how many it read, more: true that older pages remain), so --last 500 covers a 150-event conversation in two pages (total 152, pages 2, more false on the Older history conversation; --last 25 there reads one page, more true) and --from-start reads from the oldest. --kinds and --grep filter before --last counts, and pages are read until --last matching rows are in hand: --kinds MessageEvent --last 3 is the three newest messages, --kinds MessageEvent --last 500 all thirty, and --grep 'qa older 1' reaches qa older 1 on the oldest page with the default --last (count 11, pages 2). Event texts are cut at about 160 characters.
  • --observe takes the same selector syntax as the verbs: testid= and plain CSS segments (joined with >> ) are watched by an in-page MutationObserver (observedBy mutation; the Older history bullet's --observe 'testid=loading-older-events' is one), while other engines (role=, text=, nth=) are polled every 20 ms instead (not driven in this family).
  • role=button[name="Expand"] also matches "Expand thinking" and "Expand actions"; use [exact] to target tool rows. Each click flips the row to Collapse, so nth=0 walks to the next collapsed row.
  • Tool rows show no success icon; the only indicators are the exit N badge for non-zero exits and a clock icon (status-icon) for timeouts. A single action is not grouped: it renders as a bare row without event-group.
  • The model writes the tool titles and may append to commands (it turned ls /missing into ls ...; echo "EXIT:$?", exit 0). Ask for the command "verbatim, with nothing appended" when the exit code matters.
  • The agent itself may open files with canvas_ui_control (a third action in the echo/file run). Close the file tab before proving that a path link opens it.
  • On Settings → Verification the Security Analyzer field (All tab) is shown only while Confirmation Mode is on: switch Confirmation Mode on first, then pick the analyzer. Check api GET /api/settings after saving.
  • Confirmation mode with the default Security Analyzer LLM uses ConfirmRisky(HIGH): harmless commands run without a prompt. Set the analyzer to None to confirm every action, and restore LLM afterwards. The composer stays editable while waiting (status "User needed"); the buttons read Cancel / Continue.
  • The confirm and cancel shortcuts accept Meta or Ctrl and ignore a held key's auto-repeat; once a response is sent the listener is detached, so a second press sends nothing (#18127). Hints read Ctrl+↩ / Ctrl+⇧+⌫ off Apple platforms.
  • Known failure, Canvas UI (#18202, reproduced 2026-10-09; draft fix PR #18203): after a user-message branch the composer holds the prefilled text but Send stays disabled (browser enabled 'testid=submit-button' is false, and a click on it times out with element is not enabled). The prefill is a saved draft that src/hooks/chat/use-draft-persistence.ts writes into the field without an input event, and the composer is reused across the client-side switch, so its Send state keeps the source conversation's empty field. Editing the text, a reload, or Enter in the field (browser press Enter --selector 'testid=chat-input') all work. A draft restored by a sidebar switch behaves the same (F05 Gotchas).
  • Known failure, likely Agent Server: every message in a branched conversation ends with ConversationErrorEvent LLMAuthenticationError ("Your LLM API key appears to be invalid or has expired."), while the source conversation keeps working (OpenHands/software-agent-sdk#5465). Do not use branches as fixtures for other checks.
  • "The terminal session was reset because the underlying tmux server/session disappeared" (clock status-icon on the row) appears after a service stop agent-server / restart cycle. Since launch gives each run its own short TMUX_TMPDIR, other runs no longer cause it. The underlying product bug is still open (OpenHands/OpenHands#17946) for plain agent-canvas instances: bin/agent-canvas.mjs → scripts/dev-with-automation.mjs passes TMUX_TMPDIR=<state>/tmux to the Agent Server but ensureDirectories never creates it (dev-safe.mjs does), so tmux 3.4 falls back to the shared /tmp/tmux-<uid>/openhands socket and another instance's restart or stop kills this run's windows. The model usually reruns the command; expect an extra action.
  • The ConversationErrorEvent banner is not restored after navigating away and back (or a reload); read the error from the events API instead.
  • After restart, reconnection takes about 20 s without a click; click error-message-banner-retry to reconnect at once. The error banner does not come back after a page reload once the stack is healthy.
  • "Sending..." lasts about 100 to 650 ms on a local stack, too short to hover and click chat-message-stop; that stop path is not driven here. The F06.pending-messages failure path needs service stop agent-server.
  • browser clock installs a fake clock that stays with the page across reloads and client-side navigation. Install it before the goto whose page should see it, and put it back with browser clock --offset-ms 0 plus browser reload; compare browser eval "new Date().toISOString()" with date -u when in doubt.
  • The pending-message queue lives in page memory, so a failed bubble survives sidebar navigation and a backend restart but not a browser reload or browser goto of the conversation (src/stores/optimistic-user-message-store.ts). Leave and return through the sidebar when proving F06.failed-send-persists. The sidebar card's title is the first message or model-written: click its link by id, 'a[href*="/conversations/<id>"] >> nth=0'.
  • Both the pending bubble and the echoed message carry testid=user-message, so count 'testid=user-message >> has-text=...' is one too high while a failed bubble is still shown; count attempts with conversation events <id> --kinds MessageEvent (user rows: "source": "user") and the bubbles with chat-message-retry.
  • Local Stop calls the Agent Server's interrupt (Cloud pauses the sandbox instead, see pauseConversation): a command already running in the terminal is cut off, recorded as AgentErrorEvent + InterruptEvent, and after Play the model only reports the interruption.
  • The no-LLM banner reuses the home banner's test ids (home-llm-not-configured-banner). It exists only in a run where no LLM was ever configured: use a separate launch --new, never remove the profile from the shared run.
  • Read the clipboard with control-openhands browser clipboard, never browser eval "navigator.clipboard.readText()". An agent bubble that contains a code block holds two copy-to-clipboard buttons (the code block's is hidden until hovered): scope the bubble's with >> nth=0.
  • Plan-mode previews (plan-preview-*) and the BTW side messages render in this stream but are reached from the composer's mode controls; they are not mapped here.
  • Known issue #18117 (F06.thinking): Inline thinking leaks into the agent chat bubble. Live prerequisite: A live model that emits a leading inline think block on a tool-call turn, plus its finalized ActionEvent.
  • Known issue #17559 (F06.load-older-history): History pagination can show both an action and its observation (ready-for-dev companion to #16947). Live prerequisite: A real persisted history page boundary splitting an action/observation or ACP started/completed pair; dummy-key errors do not produce these tool pairs.
  • Known issue #17492 (F06.tool-visualizers): Failed browser, glob, grep, and file editor tool calls render as successful in chat. Live prerequisite: A working model emitting failed browser, glob, grep, planning-editor and file-editor observations with is_error=true.
  • Known issue #17490 (F06.tool-visualizers): Terminal commands that fail with an ordinary non-zero exit render as successful in chat. Live prerequisite: A working model producing a completed terminal command with nonzero exit_code and is_error=false.
  • Known issue #17161 (F06.markdown-rendering): markdown sanitizer lets untrusted content set rel="opener" and any target on links. Live prerequisite: A real reasoning, activated-skill, tool-description or error-detail body rendered without includeStandard. The attempted skills dialog exposed public skills only and did not reach the affected chat renderer.
  • Known issue #17026 (F06.thinking): agent-canvas: final-reply thinking block missing for text-only turns (reasoning_content not read for MessageEvents). Live prerequisite: A reasoning-capable live model emitting nonempty MessageEvent.llm_message.reasoning_content on a text-only turn, followed by reload.
  • Known issue #16842 (F06.reconnect): A late close from a replaced WebSocket marks the active connection disconnected. Live prerequisite: A transport fixture delaying an old socket close until its replacement is open; ordinary server restart cannot select that ordering.
  • Known issue #16719 (F06.live-activity): Agent status always says "thinking" even when nothing is happening. Live prerequisite: The event/status trace and deterministic stalled or errored state from the original report; its trigger is unspecified.
  • Known issue #16686 (F06.error-events): LLM errors not propagated to the frontend. Live prerequisite: A Nebius credential and its rejected DeepSeek-V3 request, or the original complete emitted error fixture.
  • Known issue #16582 (F06.stop-resume): After reconnect, a maximum-iteration error can fail to pause the agent through the legacy event handler. Live prerequisite: A maximum-iteration legacy error delivered after socket replacement, with a real model run reaching that limit.
  • Known issue #16455 (F06.events-match): Duplicate messages from the agent in 1.12.0. Live prerequisite: The original duplicated event/stream trace or deterministic prompt and timing; the report provides no reproducible trigger.
  • Known issue #16341 (F06.tool-visualizers): Code-explorer subagent frequently errors. Live prerequisite: A working model with native code-explorer delegation and the original repository task; no delegated-tool output can be generated without credentials.
  • Known issue #15626 (F06.error-events): Conversation can fail with LiteLLM auth error when database server at 127.0.0.1:5432 is unreachable. Live prerequisite: The original LiteLLM proxy/database-failure event or a disposable proxy with its PostgreSQL dependency unavailable.
  • Known issue #15606 (F06.reload-mid-run): When re-loading ACP conversations, messages disappear or move. Live prerequisite: An authenticated Codex ACP session emitting intermediate messages while another window reloads history.
  • Known issue #15527 (F06.reconnect): Canvas briefly shows "disconnected" when coming back to an idle tab. Live prerequisite: The original custom-domain deployment, idle duration and tab-suspension/network transition; those details are missing.
  • Known failure OpenHands/software-agent-sdk#5635 (F06.reconnect), reproduced 2026-10-12 at fa1f724ab573 with SDK 1.54.0 and Chromium 153.0.8010.12: sandbox pause returned 204 but an already communicating session socket remained open for more than 10 seconds. A new Canvas tab received a subsequent run:false marker while the original tab did not. DELETE returned 200 and GET returned 404, yet both held sockets remained open for more than 10 seconds; Canvas later redirected by metadata polling. Recipe: open an idle dummy-profile conversation and confirm its marker/socket; POST /api/conversations/prepare-for-sandbox-pause, retain the original tab for 10 seconds, open a second tab, POST a new unique user event with run:false, then compare the two transcripts. DELETE the owned conversation and read lifecycle metadata for another 10 seconds. Use --run <run> and browser events --kinds websocket-created,websocket-open,websocket-close --last 100; no frame bodies or auth data are recorded. Evidence: backfill-18309-surface-rechecks/F06.reconnect/pause-delete-sockets-5635.json, pause-new-tab-5635.png, pause-stale-tab-5635.png. The expected socket shutdown/reconnect remains unchanged; polling recovery must not be mistaken for socket closure.
  • Known issue OpenHands/software-agent-sdk#5465 (F06.branch-from-here): Branch from here can lose encrypted LLM credentials, so the fork fails its first turn with LLMAuthenticationError. Live prerequisite: A working cipher-backed model conversation, then fork and a successful turn on the fork.
  • Known issue OpenHands/software-agent-sdk#5447 (F06.error-events): An Anthropic Claude Opus5.5 conversation can fail with400 when its next request ends in an assistant prefill. Live prerequisite: Anthropic Claude Opus5.5 credentials and a real trailing-assistant-prefill request.
  • Known issue OpenHands/software-agent-sdk#5354 (F06.event-groups): A mixed valid/invalid tool batch can be split into separate assistant messages, breaking call/result pairing on the next provider request. Live prerequisite: A live model turn mixing valid, invalid and valid tool calls in one response.
  • Known issue OpenHands/software-agent-sdk#5225 (F06.error-events): A provider-rejected image tool result can leave the conversation permanently failing on every resume. Live prerequisite: A real provider-rejected poisoned image tool_result in a disposable persisted history, followed by retry.
  • Known issue OpenHands/software-agent-sdk#5108 (F06.tool-visualizers): The native SDK Gemini editor can report success while corrupting unrelated invalid-UTF8 bytes in the file. Live prerequisite: An Agent Server configured with the native Gemini edit tool set and a real tool edit of an invalid-UTF8 fixture; Gemini ACP uses a different external executor.
  • Known issue OpenHands/software-agent-sdk#5096 (F06.tool-visualizers): The canonical agent file editor can replace a symlink instead of editing its target; Undo does not restore the link. Live prerequisite: A working model invoking canonical file-editor str_replace/insert through a real symlink, then undo.
  • Known issue OpenHands/software-agent-sdk#5092 (F06.confirmation-mode): A revocation message sent while waiting for confirmation can implicitly approve the pending action. Live prerequisite: A working model proposing a real confirmation-gated marker action, then a revocation user message.
  • Known issue OpenHands/software-agent-sdk#5074 (F06.tool-visualizers): After server restart a native delegated child can remain Running while the parent loses its task mapping/result. Live prerequisite: A native delegated child still running during an owned server crash and parent recovery.
  • Known issue OpenHands/software-agent-sdk#5038 (F06.tool-visualizers): The agent file editor can refuse valid Chinese UTF8 text as binary when its sample splits a character; Canvas Files uses a separate preview detector. Live prerequisite: A live canonical file-editor view/edit on the specific UTF8 sample alignment; Canvas Files preview uses a separate NUL sniff, not this validator.
  • Known issue OpenHands/software-agent-sdk#4990 (F06.stop-resume): An ACP turn can keep executing tool calls while the UI already reports Paused. Live prerequisite: An authenticated Claude Code ACP turn executing separate marker-writing tools while Pause is pressed.
  • Known issue OpenHands/software-agent-sdk#4983 (F06.tool-visualizers): Agent grep can silently miss extended-regex matches when its runtime falls back to system grep. Live prerequisite: A runtime without ripgrep and a live agent grep call using extended regex and escaped literal punctuation.
  • Known issue OpenHands/software-agent-sdk#4965 (F06.error-events): A strict OpenAI-compatible provider can reject blank content in the saved history, preventing later turns. Live prerequisite: A strict OpenAI-compatible provider and a real history containing blank text or empty content arrays.
  • Known issue OpenHands/software-agent-sdk#4777 (F06.stop-resume): Stop can still start a queued tool after it finishes waiting on a resource lock. Live prerequisite: A live batch with a second real tool waiting on a declared-resource lock when Stop is pressed.
  • Known issue OpenHands/software-agent-sdk#4668 (F06.tool-visualizers): A planning editor card can lose old/new content and fail to show the complete edit diff. Live prerequisite: A live planner create/edit yielding PlanningFileEditorObservation with old/new contents for the chat diff.
  • Known issue OpenHands/software-agent-sdk#4663 (F06.tool-visualizers): Parallel Python-fallback glob calls can change process cwd and return files from the wrong workspace. Live prerequisite: Concurrent real glob/tool calls in distinct workspaces with the Python fallback active.
  • Known issue OpenHands/software-agent-sdk#4661 (F06.tool-visualizers): Same-name project subagents can resolve to another conversation workspace definition. Live prerequisite: Two native task-enabled conversations with same-name project subagent definitions and a working model for delegation.
  • Known issue OpenHands/software-agent-sdk#4583 (F06.tool-visualizers): An agent editor insertion at EOF can join onto the last line when the file has no trailing newline. Live prerequisite: A working model invoking file-editor insert at EOF on a file with no trailing newline.
  • Known issue OpenHands/software-agent-sdk#4558 (F06.error-events): A saved encrypted fallback profile can send ciphertext as its key and fail authentication, leaving only the primary error visible. Live prerequisite: A working encrypted saved fallback profile and real primary-model failure that triggers fallback resolution.
  • Known issue OpenHands/software-agent-sdk#4540 (F06.tool-visualizers): Synthetic GLM/Kimi tool calls can render as raw tool_call syntax instead of executing. Live prerequisite: Synthetic provider credentials and a GLM/Kimi turn emitting the raw tool_call wrapper.
  • Known issue OpenHands/software-agent-sdk#4532 (F06.confirmation-mode): After restarting while awaiting confirmation, approval can execute the tool but the next model request omits its assistant call. Live prerequisite: A real confirmation-paused action persisted across backend restart, then approval and a working provider call.
  • Known issue OpenHands/software-agent-sdk#4077 (F06.reload-mid-run): Retried or late streaming deltas can duplicate live text; a stalled subscriber can delay Stop and finalization. Live prerequisite: A working stream interrupted/retried or delivered to a controlled stalled subscriber, with delta/state-order capture.
  • Known issue #16947 (F06.load-older-history): Loading older history can leave both the action and its newer observation visible because UI projection depends on arrival order. Live prerequisite: A real long conversation whose REST boundary splits an action/observation pair; keep companion #17559 linked separately.
  • Known issue #18445 (F06.load-older-history, F06.reconnect): a failed initial Cloud history search can be swallowed into an empty page while the runtime count succeeds, advancing after_seq past history that was never displayed. Live prerequisite: a Cloud account and owned conversation with a genuinely failing App API events/search and a successful runtime events/count. The Local failure path throws instead and is not an equivalent check.
  • Known issue OpenHands/software-agent-sdk#5969 (F06.stop-resume): a conversation can report finished but reject Play with already-running, or delay a follow-up, while a slow event webhook is still pending. Live prerequisite: a genuinely successful model run on an owned backend with a bounded slow webhook receiver, then Play or send during that completion window.
  • Known issue OpenHands/software-agent-sdk#5740 (F06.error-events): a Responses display delta without item_id can terminate the turn with an AttributeError. Live prerequisite: the genuine provider/transformer streaming path producing that incomplete event; the report did not retain its original raw SSE. A credential-free parser diagnostic proves the SDK exception, not a Canvas model turn.
  • Known issue OpenHands/software-agent-sdk#5806 (F06.branch-from-here): editing a displayed user message first reads its parent event; if that event has become unreadable/missing on the server, the action can fail with 500 instead of a missing-event response. Live prerequisite: an owned persisted conversation with a still-displayed user message whose event storage is missing, followed by the actual Edit action. A random unknown-ID API call proves neither this UI path nor the batch-get contract, which Canvas does not call.
  • Session reconnects use the latest received after_seq cursor (F06.reconnect). For a local metadata check, retain the open page, send genuine run:false user events, record /events/count, restart the owned backend and inspect finite WebSocket lifecycle metadata: the reconnect cursor is the previous count minus one and a subsequent recovery marker appears once. This does not measure frame-body replay or Cloud behavior.
  • Stop persists paused before an in-flight model request completes (F06.stop-resume); the persisted state and reloaded Stopped UI survive an Agent Server restart. A bounded credential-free check can use the tracked accept-only endpoint and an exact listener PID proven to descend from the owned launcher; never kill by pattern or resume the paused conversation.

F07 Conversation page, header and management menu

25 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • The stop and delete confirmation buttons carry cancel-button and confirm-button; role names inside role=dialog work too.
  • stop-button, show-skills-button, show-hooks-button and show-agent-tools-button also exist in the composer, and ellipsis-button in sidebar rows: always scope to testid=chat-pane-header or testid=conversation-name-context-menu.
  • Stop Runtime (status menu) and Stop Conversation (menu) both navigate to /conversations. On a finished local conversation the agent server keeps finished; use a running conversation to see paused and Start Runtime.
  • Rename has no Escape-to-cancel: Escape leaves the input open and the next blur saves whatever was typed.
  • Escape closes the status-dot menu, as it closes the "..." and Git actions menus (browser count 'testid=server-status-context-menu' is 0). A menu opened by click or Enter returns focus to server-status-menu-trigger; one opened by hover closes without moving focus (focus left on a menu item falls to body). Touch taps cannot be driven with the CLI, so the tap path is unverified.
  • The status menu reads Running / Stop Runtime for a finished local conversation too; only a paused one shows Server Stopped / Start Runtime.
  • Panel and Overview columns animate: read their state with browser wait <sel> --state hidden|visible, not an immediate browser visible.
  • The Skills dialog shows the list read with the page or at the last Refresh: a project skill added later appears after Refresh (or browser reload), not on reopening the dialog within 10 minutes. Its own text says to click Refresh.
  • tabs-pane-header and conversation-overview-column stay in the DOM when hidden: assert with browser visible or aria-pressed, not count.
  • browser visible 'testid=usage-panel' right after Display Usage and Cost can read false; browser wait first.
  • Exports and the zip land in <run>/private/downloads; the transcript holds the prompts and the zip holds the full event store. Review before sharing.
  • --observe accepts the same selector syntax as other verbs ('testid=chat-messages-skeleton') as well as CSS.
  • A sidebar row click lands on /conversations/<id>?backend=default-local: end --expect-url regexes without $ there.
  • role=button[name="Pull"] also matches Pull Request; add [exact].
  • The Open Workspace folder browser has no path field: walk it with folder-browser-up and folder-browser-entry-<name>. fixture git-repo puts the repo in <run>/workspace, three levels above the workspace favorite.
  • The repo, branch and Pull/Push chips come from git remote get-url origin in the workspace, read when the page loads: reload after changing the remote.
  • The Usage tab logs GET /api/llm/balance 404 on a local backend (F27); count it in browser errors --app-only but it is not an F07 failure.
  • The Overview drawer has no Automations, Skills, MCP or Secrets sections. The Overview menu offers Workspace and Git rows only, and its drawer opens only Pull Requests (F27.overview-drawer).
  • Dialogs move focus inside when they open and keep Tab inside: the Skills and Hooks dialogs start on their close button, Stop and Delete on cancel-button, Export on close-transcript-export-modal, Agent Tools on close-system-message-modal. In the Hooks dialog, focusing Refresh with Tab opens its tooltip, which takes the first Escape; a second Escape closes the dialog.
  • The Hooks dialog shows the workspace's hooks.json as of its last read (its first open for the conversation after a page load, or Refresh), not the hooks the conversation started with: after the hooks steps above, browser goto /conversations/<git-id> (started before the file existed; its hook_config is null) and opening the dialog lists both events too. A reopen within 5 minutes of that read shows the same list and sends no request: click Refresh after editing hooks.json.
  • A prompt- or agent-type hook shows its text under Commands (the prompt, or an agent hook's system_prompt). With the Hooks dialog closed (the hooks steps above end that way; otherwise control-openhands browser click 'testid=close-hooks-modal'), write <run>/workspace/qa-f07-repo/.openhands/hooks.json as {"stop":[{"hooks":[{"type":"prompt","prompt":"QA_F07 prompt hook"}]}],"session_start":[{"hooks":[{"type":"agent","system_prompt":"QA_F07 agent hook"}]}]}, run control-openhands browser goto /conversations/<hooks-id>, open the menu and control-openhands browser click 'testid=conversation-name-context-menu >> testid=show-hooks-button', click testid=hooks-modal >> testid=refresh-hooks, then role=button[name="Session Start 1 hook"] and role=button[name="Stop 1 hook"] inside testid=hooks-modal (an event expanded before a Refresh stays expanded, and a second click collapses it, so start from a reopened dialog). In control-openhands browser snapshot 'testid=hooks-modal' they expand, in that order, to Matcher * Commands QA_F07 agent hook Type: agent Timeout: 60s and Matcher * Commands QA_F07 prompt hook Type: prompt Timeout: 60s, and browser text 'testid=hooks-modal >> pre' returns QA_F07 agent hook and QA_F07 prompt hook (control-openhands browser screenshot --feature F07.hooks-modal --name prompt-agent-hooks). An agent hook without system_prompt shows no box: with {"session_start":[{"hooks":[{"type":"agent"}]}]}, close, reopen and Refresh, Session Start 1 hook expands to Matcher * Commands Type: agent Timeout: 60s and browser count 'testid=hooks-modal >> pre' is 0.
  • An agent hook shows its system_prompt, the text it runs with, even when it also carries a prompt, and an agent hook with only a prompt shows no box. With {"session_start":[{"hooks":[{"type":"agent","prompt":"QA_F07 stray prompt","system_prompt":"QA_F07 agent system prompt"}]}],"stop":[{"hooks":[{"type":"agent","prompt":"QA_F07 prompt only"}]}]} in hooks.json, close, reopen and Refresh the dialog, then expand both events: Session Start 1 hook reads Matcher * Commands QA_F07 agent system prompt Type: agent Timeout: 60s, Stop 1 hook reads Matcher * Commands Type: agent Timeout: 60s, and control-openhands browser count 'text=QA_F07 stray prompt' and browser count 'text=QA_F07 prompt only' are 0 (browser screenshot --feature F07.hooks-modal --name agent-hook-system-prompt). Agent hooks in hooks.json really run on the next turn in that workspace (the session-start one shows a Hook: SessionStart failed row), so write the recipe's two-event hooks.json (pre_tool_use and stop) back and Refresh afterwards.
  • With the Agent Server down, Refresh in the Hooks dialog shows its own fetch error (#18177): after control-openhands service stop agent-server on an open dialog, refresh-hooks and browser wait 'testid=hooks-modal >> text=Failed to fetch hooks' --timeout 30000, the dialog reads Failed to fetch hooks. Please try again later. under its explanation (browser count 'testid=hooks-modal >> text=Failed to fetch skills' is 0). The 502s and the closed WebSocket that browser errors --app-only then lists are the outage itself. Run it last, or restart afterwards.
  • After the user-message branch in F07.branch-from-message, Send stays disabled next to the prefilled text (browser enabled 'testid=submit-button' is false; Known failure #18202, see F05 Gotchas). Enter in the field sends it.
  • Known issue #18136 (F07.cloud-only): Connect Repo on a cloud conversation does not save the repository on the server. Live prerequisite: An authenticated Cloud sandbox/repository provider and a second fresh browser to compare server repository metadata.
  • Known issue #17494 (F07.export-transcript): Transcript export leaks raw i18n template for ACP tool calls without a title. Live prerequisite: An authenticated ACP session that emits a titleless tool call, then Markdown and HTML export.
  • Known issue #16876 (F07.history-skeleton): Planning-agent history flag stays true forever when sub-conversations fetch fails — infinite chat loading skeleton. Live prerequisite: A real planning sub-conversation whose batch lookup fails or yields null, with the main history available.
  • Known issue #16727 (F07.open-by-url): Conversation corrupted after VM restart: "Missing event file". Live prerequisite: A disposable VM interrupted at the event-write boundary or the original missing-event persisted fixture.
  • Known issue #16356 (F07.open-by-url): Conversation LLM config is frozen at creation time — resuming after API key rotation fails permanently with LLMAuthenticationError. Live prerequisite: A working provider key that can be rotated/revoked, an existing conversation using it, and a replacement profile.
  • Known issue #15543 (F07.git-control-bar): Bad parsing for branch retrieval. Live prerequisite: The original Git probe stdout/interleaving that temporarily puts a filename in the branch badge; dirty state alone is not a deterministic trigger.
  • Known issue OpenHands/software-agent-sdk#5640 (F07.download-zip): Download conversation data can return404 after a warm-pool server initializes a custom conversation-storage path. Although closed upstream, the pinned SDK 1.55.0 still reads the default configuration path here; this remains a supported-version distinction, not a freshly reproduced failure. Live prerequisite: A deferred-init warm-pool Agent Server initialized with a non-default conversations_path; this launcher starts an already-initialized server.
  • Known failure OpenHands/software-agent-sdk#5629 (F07.rename-inline), reproduced 2026-10-12 at fa1f724ab573, SDK 1.54.0. A second server's native first load rewrote a newer saved title with its startup copy; the connected Canvas header showed that older title. Recipe: create an idle conversation on A and PATCH its title to QA_SHARED_BEFORE_B_5629_REPEAT; run pinned SDK Python python .agents/skills/verify-openhands/scripts/fixtures/shared-storage-server.py --run <run> --seconds 300 in the background. It starts only a bounded no-editor SDK B sharing this run's conversation directory/cipher/session key; its private server.json supplies the port and owned operator/PGID. After B listens, PATCH A to QA_SHARED_AFTER_B_5629_REPEAT and confirm disk. Run control-openhands service stop agent-server --run <run>, then python .../fixtures/shared-storage-server.py --run <run> --load-id <id>: native B /events/count returned 200 and disk reverted to BEFORE_B. Restart A only to serve Canvas, choose default-local and doctor; use the Add Backend recipe from F25 to connect B at its recorded port with the private key file, then open its card: the header reads BEFORE_B. SIGTERM only the recorded helper operator and verify its port/PGID gone; the helper also bounds runtime and terminates remaining group descendants. Evidence: backfill-18309-surface-rechecks/F07.rename-inline/metadata-repeat-before-b-load-5629.json, metadata-repeat-after-b-load-5629.json, shared-first-load-reverted-5629.png. The native first-load request is explicit; stale catalog display alone is insufficient proof of this overwrite.
  • Known failure OpenHands/software-agent-sdk#5539 (F07.download-zip), reproduced 2026-10-12 at fa1f724ab573, SDK 1.54.0: three real overlapping receivers of one idle conversation made 929 requests in 20 seconds, including 31 completed corrupt ZIPs with status 200, 106 server 500s, 269 ingress 502s and 133 RemoteProtocolErrors. A sequential 2,000-request control returned valid ZIPs. Recipe (use the pinned SDK Python with httpx): python .agents/skills/verify-openhands/scripts/fixtures/download-trajectory-overlap.py --base-url <own-base-url> --conversation-id <id> --api-key-file <run>/private/session-key --output-dir <run>/private/trajectory-overlap --seconds 20 --clients 3 --rounds 5000 --slow-ms 2. It enforces loopback, request/duration/network/uncompressed/member budgets and bounded CRC reads; aborts and unsupported archives remain unverified. While it runs, use browser goto /conversations/<id>, open chat-pane-header >> ellipsis-button, click download-trajectory-button, then browser downloads --last 3 --inspect (all CLI commands with --run <run>). That Canvas click downloaded an inspectable ZIP; its particular result is not claimed corrupt. Private raw samples stay private. Evidence: backfill-18309-surface-rechecks/F07.download-zip/trajectory-overlap-5539-summary.json and overlapping-download-5539.png. The summary correction excludes two ambiguous Content-Length differences that could be intentional deadline aborts; only completed corrupt ZIPs count as corrupt downloads.
  • Known issue OpenHands/software-agent-sdk#5094 (F07.open-by-url): After ACP session/load fails, the backend can create a fresh model session while Canvas still shows the old context. Live prerequisite: An authenticated Codex ACP session whose original session/load fails while persisted history remains.
  • Known issue OpenHands/software-agent-sdk#4893 (F07.status-menu): A sandbox frozen mid-run can resume with a stale Running state that rejects normal resume requests. Live prerequisite: A disposable sandbox/VM frozen mid-run with a live model and persisted ownership state.
  • Known failure OpenHands/software-agent-sdk#4800 (F07.rename-inline), reproduced 2026-10-12 at fa1f724ab573, SDK 1.54.0. A refused rename changed the server's GET title while meta.json kept the previous title; graceful shutdown subsequently persisted the rejected title. Recipe: create an idle dummy-profile conversation, then double-click conversation-name-title, fill conversation-name-input with QA_META_BEFORE_4800 and press Enter; verify GET/disk. Record the original mode and temporarily chmod 0500 <run>/private/state/dev_conversations/<id-hex> to deny an atomic metadata replacement. Rename through that UI to QA_META_REFUSED_4800: the permission-denied toast/HTTP 500 appears, GET shows REFUSED, disk still shows BEFORE. Restore the exact original directory mode in finally, then restart, doctor, GET and reload (CLI with --run <run>): REFUSED was persisted during graceful shutdown. Only the owned temporary directory is changed; evidence contains title/timestamp fields, not stored secrets. Evidence: backfill-18309-surface-rechecks/F07.rename-inline/metadata-before-4800.json, metadata-denied-4800.json, refused-title-4800.png, metadata-after-graceful-restart-4800.json. Tags/autotitle and ownership-conflict branches remain untested.
  • Known issue #16877 (F07.open-by-url): Untitled terminal-status conversations are reported to keep the 3-second title polling interval. On frozen target fa1f724ab5737a17453ad6081129eb91c5a423c6, a real Local autotitle:false/idle conversation retained title:null, while eight browser batch GETs had 30.008–30.031-second intervals over 210 seconds. The direct adapter supplies a default display title. This does not exercise the Cloud null-title IDLE/FINISHED branch, which needs an approved Cloud conversation with a failed/unsupported title job. Run backfill-18309-coordinator-checks, Agent Server/SDK 1.54.0, automation 1.19.3; retained evidence/F07.open-by-url/16877-local-polling.json and 16877-local-idle.png. Arrange the empty conversation with api POST /api/conversations --write --data @<idle-autotitle-disabled-payload>, open /conversations/<id>, clear browser network, leave the tab foreground, then read browser network --filter ids= --last 100 and conversation status <id>. No model call or response was used.

F08 Workspace drawer: files and changes

34 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • When the drawer is closed, its tabs stay mounted at width 0. browser count 'testid=files-tab' is 1 even then, so assert with browser visible or right-panel-toggle aria-pressed.
  • The tab row is a tablist "Conversation panels" of role=tab controls, each named after its tab even when inactive (#17612), so role=tab[name="Commits"] finds the Commits tab whether or not it is active; role=button[name="Commits"] finds nothing. aria-selected marks the selected tab. The conversation-tab-* test ids still work.
  • The tab row ends with the ellipsis menu (Customize tabs), then the VSCode button (drawer-vscode-link, named Open in VS Code) when the runtime reports a running editor. #17660 removed that button and #18048 restored it, so on a checkout between the two the drawer has no editor control at all.
  • The overflow menu is not a role=menu. Escape closes it and returns focus to the tab bar's ellipsis-button, both in the desktop drawer and on the phone /panel page; choosing a row or an outside click (browser mouse-click) closes it too.
  • The Files list and the open preview do not follow bash edits: use-auto-refresh-files-on-edit.ts refreshes the file queries only for file-editor observations, and its comment says shell observations skip them on purpose (so that every shell command does not reload canvas iframes). This map still expects files the agent writes to appear (F08.files-auto-refresh), so the bash case stays a fail row. Click files-tab-refresh once before asserting on files the agent made with the shell. Since #18032 that one click also re-reads the open file (the content fetch revalidates instead of using the browser HTTP cache, and the rich preview gets a new ?v=), and a page reload shows the current body without a click.
  • The chat also fetches the file list (to linkify paths). A conversation page left open while the agent works can therefore show a stale tree the moment the drawer first opens.
  • Binary detection is a NUL-byte sniff of the first 8 KB, and the Office message also needs that sniff to trip. A 4-byte PK\003\004 "pptx" or random bytes without a NUL render as text in the plain viewer. Fixtures must contain a NUL.
  • control-openhands fixture folder writes a plain folder with notes.md, but by the time the conversation has run the folder holds an empty .git, so a conversation that leaves notes.md in place shows Uncommitted 1 file rather than the empty state. Delete it first (as in the recipe).
  • Diff views are Monaco. testid=file-diff-viewer and file-single-viewer are not on the DOM; read text through testid=editor-container. The narrow drawer falls back to an inline (not side-by-side) diff. The view-mode buttons are named Old version, Diff and New version, and collapse is named Expand or Collapse after its state (#18033).
  • browser eval "navigator.clipboard.readText()" hangs for about 2 minutes (no clipboard permission). Use control-openhands browser clipboard to read what Copy wrote.
  • browser click ... --observe accepts the usual selector syntax (testid=...).
  • Two .cursor-ew-resize grips sit in the conversation page (chat/drawer, then the Files tree); scope the tree one with testid=files-tab-tree-resize-handle.
  • At phone width, chat path links (and the agent's canvas_ui_control tab and file requests, through src/services/canvas-ui.ts) navigate to /conversations/<id>/panel (#18045); at desktop width they open the side drawer and the URL does not change. A /panel URL above 1024 px redirects to /conversations/<id>, so a bullet that leaves the phone viewport on /panel ends on the chat route.
  • If the agent's terminal resets mid-command ("terminal session was reset", for example after a restart), the agent reruns the command. Check the workspace with git status through a second prompt rather than trusting the first observation. Runs no longer share a tmux server (OpenHands/OpenHands#17946).
  • Known issue: #17567 (Monaco disposed-model page error on diff unmount; it also fires on collapse and on a diff→old switch).
  • Known issue #18108 (F08.files-tree): Files tab shows "No files in workspace" on cloud when the agent writes outside /workspace/project. Live prerequisite: An authenticated Enterprise Cloud sandbox, no repository selected, with files outside its default working directory.
  • Known issue #17773 (F08.files-tree): Files tab always shows "No files in workspace" on Windows local backend. Live prerequisite: A Windows local Agent Server using cmd.exe; this run is macOS.
  • Known issue #16949 (F08.files-refresh): Keep Files and Changes views source-driven after refresh. Live prerequisite: An authenticated Cloud repository for the refresh-query mismatch, or real file-editor observations causing an in-place M-to-D Git refetch.
  • Known issue #16716 (F08.uncommitted-changes): Diff view is regularly out of sync. Live prerequisite: The original simultaneous-agent/shared-repository state or a deterministic stale-diff trigger; the report only suggests that timing.
  • Known issue #16689 (F08.files-load-error): When workspace/ paths throw a 401, call the workspace-session endpoint. Live prerequisite: The original fresh-VM URL, auth/cookie state and exact request sequence; the report says the trigger is unclear.
  • Known issue #16454 (F08.uncommitted-changes): Diff list is showing duplicate entries. Live prerequisite: The original Git changes response and update sequence producing duplicate rows; the report provides no trigger.
  • Known issue #16331 (F08.files-tree): file preview panel stops working during streaming. Live prerequisite: A real streaming/tool-running conversation while selecting a second workspace file.
  • Known issue #15692 (F08.uncommitted-changes): Diff still shows after agent has committed and pushed. Live prerequisite: A real agent commit/push terminal observation while the diff remains mounted, plus a disposable Git remote.
  • Known issue #15567 (F08.files-load-error): Workspace file preview returns 401 Unauthorized on non-HTTPS non-loopback hosts. Live prerequisite: A plain-HTTP non-loopback deployment and browser accessing that origin, with workspace-cookie observation.
  • Known issue #15519 (F08.files-load-error): "files" tab in Files side panel throws 401 Unauthorized. Live prerequisite: The reported Linux Docker deployment/auth topology; this isolated run uses the macOS source launcher.
  • Known issue #15501 (F08.files-rich-plain): Generated-page JavaScript was blocked by an iframe sandbox. Files Rich intentionally disables scripts; the report does not identify whether Files or Browser was used. Live prerequisite: The original preview panel and file URL or minimal page identifying the intended surface; Files Rich deliberately blocks scripts, while Browser is separate.
  • Known issue OpenHands/software-agent-sdk#4562 (F08.files-load-error): Plain-HTTP non-loopback workspace previews can return401 after the browser rejects the workspace-session cookie. Live prerequisite: A plain-HTTP non-loopback same-origin browser deployment with real workspace-cookie observation.
  • Known issue #16871 (F08.vscode-link): the shared transformVSCodeUrl still accepts arbitrary schemes, and the current Cloud use-unified-vscode-url.ts branch passes sandbox exposed_urls editor values through it without the Local origin gate. Live prerequisite: an authorized Cloud account/sandbox whose editor exposed_urls value can be controlled for safe URL-scheme rejection checks. Local inspection on 2026-10-12 at fa1f724ab573 found no Download via VS Code action in the real card menu: its only production caller does not supply the required showOptions prop. The current drawer opens with noopener,noreferrer. Neither the old opener exploit nor the remaining Cloud scheme scenario was reproduced. Evidence: backfill-18309-surface-rechecks/F04.download/vscode-card-action-absent-16871.png and its ARIA snapshot. Keep this linked to the live editor-link consumer; do not infer the whole issue resolved from the removed card action.
  • Known issue OpenHands/software-agent-sdk#5845 (F08.vscode-link): reserved characters in the workspace path can split the editor URL's unencoded folder parameter and open the wrong folder. Live prerequisite: a supported origin serving a genuinely enabled/running editor and an owned conversation in a path containing & or #. A direct URL-constructor call does not exercise the link; Cloud exposed_urls takes another path.
  • Known issue OpenHands/software-agent-sdk#5784 (F08.files-rich-plain, F08.files-fallbacks, F08.files-open-new-window): a path-prefixed Agent Server can set a workspace cookie whose Path omits the prefix, leaving HTML/image/file previews unauthorized. Live prerequisite: an authenticated path-prefixed owned Local server with OH_WEB_URL and a real cookie-authenticated preview; the ordinary root-path launch cannot exercise that cookie Path.

F09 Settings shell and navigation

26 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • browser click returns the URL before client-side navigation and redirects settle (it prints / or /settings). Always follow a navigating click with browser wait-url '<regex>$' before asserting.
  • At phone width there are two backend-selector-settings-link elements (hidden desktop sidebar plus the drawer); scope with testid=sidebar-mobile-drawer >> ... or the click fails in strict mode. The hub's nav links share testids with the desktop nav, so scope them with testid=settings-mobile-hub >> ....
  • The page does not scroll inside the settings <main>; the app shell column does. browser scroll <selector> --by N only scrolls the element into view and ignores --by; hover the content first, then browser scroll --by N (mouse wheel at the pointer).
  • Only /settings/app is taller than a 1000 px viewport in a fresh run; use it for the pinned-nav check.
  • Visiting /settings/agents ticks the checklist item "Customize your agent" for good (struck through, still a working link, so the bullet order above is fine), and configuring a model hides the Home banner. Run the banner deep link before llm preset on a fresh run.
  • Registry reachability depends on the sandbox and changes between runs. When the browser cannot reach registry.npmjs.org (seen as ERR_CERT_AUTHORITY_INVALID through a proxy), the version badge and sidebar tile stay hidden and the dialog shows the failure state; that is environment, not product. When it can, a checkout at the latest release shows "Up to date" and the failure text cannot be produced. Check browser network for the registry request before deciding which update rows are blocked.
  • The update dialog's role="dialog" wrapper has no accessible name, so role=dialog[name=...] does not match it; use testid=agent-canvas-update-modal.
  • The /settings → /settings/agents redirect only happens at ≥1024 px. Below that (phones and tablets) /settings is the hub; do not expect a redirect there.
  • From 768 to 1023 px the sidebar rail stays and the phone top bar is gone; the Settings selector above each subpage provides sibling navigation. The rail's gear (backend-selector-settings-link) still returns to the hub.
  • The phone hub is indented 32 px while sub-pages start at 16 px (hub padding inside the page padding); that is current layout, not overflow.
  • The "LLM Profiles" rename in useSettingsNavItems targets a /settings nav item that does not exist, so it never applies; the LLM nav item always reads "LLM".
  • Known issue #17508 (F09.update-available): After installing a newer package, an older running bundle can keep showing its own version and an update banner. Live prerequisite: Isolated old/new package installs with the old process still serving, or the original stale bundle/cache capture after update.
  • The public SettingsNavigation export renders the legacy SettingsMobileDrawer with a half-opacity black scrim (bg-black/50). Check its modern and fallback compiled CSS as a build contract. The routed phone hub uses SettingsMobileHub, so its navigation recipe does not exercise the exported drawer; visual verification of that export needs an embedding host.

F10 LLM profiles

34 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Save always runs a real completion against the provider (except for connection-linked profiles). A bad or missing key fails fast with the provider's message; budget one tiny call per Save.
  • The editor's starting tab is inferred from the values it loads. Edit opens on whichever tab the profile's non-default values need (QA_renamed and QA_adv opened on Basic, QA_broken on All). A new editor opens on Basic only while the agent's LLM settings are all defaults: once the Default profile has a temperature (the schema step) it opens on All, and this survives deleting every profile, because the agent settings keep the last applied values. Click the tab you need before touching its fields, or llm-provider-input / llm-custom-model-input / llm-api-key-help-anchor will not exist.
  • control-openhands service stop agent-server cannot show this page's error states: the whole app switches to the disconnected-backend screen.
  • In Edit, llm-api-key-input holds the stored encrypted key token (about 140 masked characters), not an empty field. Never run browser value on it: it prints the token. Leave it untouched to keep the key.
  • An open provider or model listbox intercepts clicks on the editor's buttons (cancel-profile-btn, save-profile-btn time out with "subtree intercepts pointer events"): browser press Escape first.
  • Provider and model options render in a portal outside the editor and modals: select testid=provider-item-<name> unscoped. Only verified providers have provider-item-* IDs and only Other models have model-item-* IDs; use role=option[name="..."][exact] for the rest. browser testids --filter model-item lists only items scrolled into view; browser count finds the rest.
  • The auto-filled Profile Name never reuses an existing profile's name: picking a model whose name is already a profile's (for example deepseek-flash after llm preset deepseek) empties the field, which disables Save. Recipes fill profile-name-input with a QA_* name anyway.
  • A duplicate profile name is flagged: aria-invalid true and a taken-name alert. The alert has its own wording, An LLM profile with this name already exists. ….
  • The Rename modal does not check duplicates client-side (Rename stays enabled); the server's 409 comes back as the toast Profile '<name>' already exists, and browser errors --app-only counts one http-error and one console.error for it.
  • Set as default (and deleting the Default) rewrites the agent's active LLM settings (control-openhands llm show). Deleting every profile leaves the last applied model and key in agent settings.
  • The app never leaves profiles without a Default: src/routes/root-layout.tsx runs useEnsureActiveProfile (src/hooks/use-ensure-active-profile.ts), which activates the first profile with a key (else the first profile) whenever profiles exist but none is Default, whether never activated or because the Default was deleted (this is what promotes a profile in F10.delete-default). So llm set --no-activate keeps the Default only while another profile is Default: on a run whose only profile is the arranged one, control-openhands api GET /api/profiles --pick active_profile flips from null to it at the next page load (browser reload) and the agent's LLM settings follow (llm show).
  • Profiles created by Add models are named from the model (deepseek-chat, …), not QA_*. The chooser hides a model when a profile with that name exists, not when a profile uses that model. Clean them up before the connection: the server refuses to delete a connection while profiles reference it.
  • A profile linked to a missing connection is grouped under a header showing the raw connection id (00000000…) instead of a name.
  • The All tab logs many [i18n] Missing translation for key "SCHEMA$LLM$..." console warnings; they are not page errors.
  • The list follows the server's order, which sorts by profile file name: QA_pro-copy-1 lists before QA_pro-copy (- sorts before .json's .). Do not assert a strict alphabetical order on names that are prefixes of each other.
  • Toasts last only a few seconds. A browser screenshot taken after a slow command can miss them, so assert with browser wait-text or browser toasts.
  • A new profile keeps the inherited LLM options the editor shows, such as temperature.
  • Subscription models are not loaded yet. uses the translated SETTINGS$SUBSCRIPTION_MODELS_NOT_LOADED copy in settings and onboarding. It shows when a subscription profile is saved before GET /api/llm/subscription/openai/models answers; checking that branch needs the real request to be pending or failed.
  • Known issue #17785: Saving DeepSeek-v4-flash on Windows Desktop can fail with missing cache_creation_tokens instead of saving. The reported path needs a Windows Desktop 1.24.0 environment with its SDK dependency resolution and an approved DeepSeek key; this macOS source run cannot establish that result. (F10.create-validation).
  • Known issue #17706: A freshly resolved npm-launcher environment can reject openhands/claude-* conversations because LiteLLM sends prompt_cache_key. Reproduction needs an approved OpenHands proxy key with an Anthropic model and the affected fresh transitive dependency resolution; a keyless initialization cannot prove provider acceptance. (F10.default-used).
  • Known issue #17391: ChatGPT Business subscription login can be refused or use a fallback model with a 404. Completing the entitlement and selected-model checks needs an approved ChatGPT Business workspace login and the reported Docker subscription runtime. (F10.subscription).
  • Known issue OpenHands/software-agent-sdk#5181: The SDK can turn an advertised output limit into a requested default cap and silently drop an explicitly enabled reasoning_effort. The narrowed maintainer scope needs an authenticated OpenRouter/proxy route exposing lower-limit endpoints and capability metadata, or a real endpoint accepting the explicit override; a synthetic empty-metadata response would not establish production behavior. (F10.schema-validation).
  • Known issue OpenHands/software-agent-sdk#4934: Switching to a MiniMax M3 profile can fail because the SDK sends unsupported reasoning_effort. The runtime switch needs approved working credentials for the starting model and MiniMax route; the reported provider rejection cannot be proved with a loopback connection error. (F10.schema-validation, F10.default-used).
  • Known issue OpenHands/software-agent-sdk#4613: A namespaced model such as openai/openai/example-model can have its inner provider prefix stripped again in Responses API and reach the provider as example-model. Reproduction needs an approved OpenAI-compatible endpoint serving the namespaced model and its exact stored API-mode settings; a loopback connection refusal cannot prove the outbound identifier. (F10.custom-model).
  • Known issue OpenHands/software-agent-sdk#4511: Disabling prompt caching can still emit cache_control on preexisting cache-marked tool messages. The report only supplies a Python serializer reproduction; a Canvas check needs the exact persisted/tool-message precondition and a provider request that exposes the cache markers, with approved model access. (F10.schema-validation).
  • Known issue OpenHands/software-agent-sdk#4250: A Cloud Workers AI configuration can fail on context-window validation or be routed as OpenAI with an invalid Cloudflare key. Reproduction needs a disposable OpenHands Cloud account, its Workers AI endpoint/key and the exact saved model configuration; the report supplies no complete configuration steps. (F10.custom-model, F10.default-used).
  • Known issue OpenHands/software-agent-sdk#4247: An LM Studio configuration can fail with LLM Provider NOT provided although its models endpoint works. Reproduction needs the reachable LM Studio service and the exact model ID/provider-qualified config, which the report does not supply as text; the reported CLI/Docker SDK 1.16.1 installation is not this source stack. (F10.custom-model).
  • Known issue OpenHands/software-agent-sdk#5890: Switching the conversation LLM can lose the managed-key refresh-on401 hook. Blocked: needs an approved managed-key refresh source and an allow-listed real model endpoint that returns401 and accepts the refreshed key. Refused loopback cannot establish the refresh/retry outcome; do not supply a fake successful completion. (F10.edit, F10.default-used).
  • Known issue OpenHands/software-agent-sdk#5862: DeepSeek invalid-key401 responses can be classified as bad-request errors. Blocked by this pass's loopback-only provider routing: a real invalid-key validation request to DeepSeek must be authorized to capture its actual401 shape. No usable key or successful inference is needed, but a loopback connection error does not prove this classification. (F10.create-validation).
  • Known issue OpenHands/software-agent-sdk#5838: A live LLM switch during a running tool step can be acknowledged before the new LLM is persisted for GET/restart. Blocked: needs a genuine model-issued bounded tool step (for example sleep 20) and an approved starting model. Refused-loopback initialization never enters that step. (F10.default-used).
  • Known issue #17806: A model-only Save must preserve an untouched key. Current SDK1.55 refused-loopback validation rejects Save before persistence; the stored key marker remains set. Completing Basic/Advanced preserved-key and runtime-switch proof needs an approved route that accepts preflight. Keep dummy traffic on refused loopback until an external route is authorized; a rejected Save does not establish either key loss or successful preservation through Save. (F10.edit).

F11 Provider connections

21 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Select the Provider by clicking an option (testid=provider-item-<name> exists only for verified providers; use 'role=option[name="<label>"]' for the others), or with browser choose 'testid=provider-connection-provider-input' <label>. browser fill alone types a filter that is cleared on blur and leaves Save disabled.
  • browser fill ... --value-file <key file> needs no positional value and prints only the length; keep keys out of commands and screenshots (the field is a password input).
  • Duplicate display names are accepted (two rows called QA_conn), and so is any base URL text; rows then differ only by order ('testid=provider-connection-row >> nth=1'). Keep fixture names unique.
  • Edit's Save is enabled even with no changes, and a blank key on Edit is omitted from the request (the stored key is kept); there is no way to clear a key.
  • The delete spinner (aria-busy) lasts under 100 ms on a local stack; --observe sees only Delete then <absent>.
  • role=dialog matches two nested elements in these modals; count > 0, do not expect 1.
  • The first profile added on a run with no profiles becomes the active one. Activating a linked profile copies the resolved key into the active agent settings, which keep the connection id: that is why the server still counts a reference after every linked profile is gone (see F11.delete-stale-reference) (OpenHands/software-agent-sdk#5498).
  • Delete a connection's linked profiles before the connection; then also make an unlinked profile active, or the delete is refused.
  • Rotation results are read from the conversation (conversation events ... --kinds ConversationErrorEvent,MessageEvent and the banner in the chat), not from the settings page, which never shows the key.
  • Model Router's Add connection reuses this modal (F12); its toasts and fields are the same.
  • F11.load-error needs a real server failure: the recipe corrupts this run's own provider_connections.json and restores it. Always restore it (keep the .bak), or every later family on the run sees the error; never touch another run's state.
  • At phone width a long name squeezes the provider column to one letter (d…); the full values stay in the API and the name's title.
  • Known issue OpenHands/software-agent-sdk#5497: after a key rotation, new conversations still run agent steps on the old key; only title generation uses the new one (F11.rotate).
  • Known issue OpenHands/software-agent-sdk#5207: OpenRouter linked profiles can lose connection credentials at raw agent/condenser startup or reuse stale credentials after rotation; opaque reasoning can also be lost on persisted turns. End-to-end checks need an approved OpenRouter connection and a model returning reasoning_details, including the linked-profile switch and reload path; loopback failure does not prove them. (F11.agent-uses-connection, F11.rotate).

F12 Model router (meta-LLM profiles)

25 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Settings nav test ids embed the route: quote 'testid=sidebar-settings-/settings/meta-llm' as written.
  • The empty-state hint about LLM profiles only exists before any profile is saved: drive F12.empty-states first on a fresh run.
  • The first router you create is activated and turns Run on first message on by itself; later routers do neither. Any recipe that creates a router on an empty list changes both.
  • Run on first message is an sr-only checkbox (keyboard reachable): click the label text inside testid=meta-profile-run-at-conversation-start and read .checked/.disabled with browser eval. The stored preference (misc_settings.app_preferences.run_router_at_conversation_start) is independent of what the disabled switch shows; with no active router it may still be true. Turn it off before deleting the last router, or a later "first router" will not re-enable it because it is already on.
  • Router templates name 11 models (gpt-5.4 … claude-sonnet-4-5) that will not exist in a sandbox. With a provider connection present, a template preselects it, and saving creates all 11 as LLM profiles linked to that connection unless you pick Don't create profiles first (F12.create-more does). If a run created them, remove them with for p in gpt-5.4 gpt-5.5 minimax-m3 claude-opus-4-6 claude-opus-4-7 claude-opus-4-8 claude-fable-5 kimi-k2.5 kimi-k2.6 deepseek-v3.2-reasoner claude-sonnet-4-5; do control-openhands api DELETE /api/profiles/$p --write; done (they live in the run's private state and only add rows to Settings → LLM).
  • Profile matching is case-insensitive and exact by name: table rows are - <profile name>: description; existing deepseek-flash/deepseek-pro are not re-created, and the endpoint is derived as <provider lowercased>/<name>.
  • The classifier and the model table refer to LLM profile names, not model ids. A router whose classifier profile does not exist (for example a template's minimax-m3 without a connection) saves fine and only fails when a conversation tries to route.
  • The agent decides whether to call route_task_to_model: a prompt that conflicts with routing (Reply with only the word pong.) made deepseek-flash skip the call even with the instruction present. Use a task-like prompt for the positive check and assert the system prompt via events/search for the instruction itself.
  • conversation events does not print the system prompt; read it with control-openhands api GET "/api/conversations/<id>/events/search?limit=1" --max-bytes 200000 and grep the output.
  • service stop agent-server does not show this page's error state: the whole app switches to the Manage backends card (F02). Run control-openhands restart afterwards.
  • The selection in the classifier and connection pickers is a combobox: open it with a click on the input, then click role=option[name="..."]; browser fill on the classifier sets a free-text value (allowed) instead of picking a profile.
  • At 390 px the row truncates the name (QA_route…) and summary; read names with browser text, not the screenshot.
  • The Model table placeholder displays separate lines (F12.template-custom).
  • Known issue OpenHands/software-agent-sdk#5878: Router files can be written under a literal ~ directory when Agent Server was configured with leading-tilde OH_PERSISTENCE_DIR. Blocked on the normal absolute-path launcher fixture: needs a disposable server with private HOME/cwd and literal ~/... or relative persistence configuration, then a router save and filesystem readback. Never point it at user state. (F12.create-first, F12.list).

F13 Agent profiles

22 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • The editor is not a route: browser url stays /settings/agents and browser reload drops unsaved edits back to the list. The settings page header (settings-page-subtitle) is hidden while it is open.
  • Autocomplete dropdowns: clicking an input that already shows a value can open an empty listbox (the text filters it), and an option click then times out. The toggle-button opener does not always open the list either: on the Edit editor's agent-profile-llm-selector (bottom of the form) the option click timed out 5 of 5 times right after opening the editor, and the model list's Custom option once detached mid-click after a preset change. browser choose avoids the input-filtering problem, but an option may still remain unstable immediately after opening an editor. Reload and reopen before retrying; the suffix-identity check can use the already-selected active LLM without changing that dropdown. A listbox also closes if you spend a command (snapshot, eval) in between, so click the option right after opening.
  • A taken name marks the field invalid with an alert reading An agent profile with this name already exists. ….
  • has-text=default also matches the active row's Default badge (Playwright's text match ignores case), so with several profiles 'testid=agent-profile-row >> has-text=default' hits two rows; select the seeded profile with 'testid=agent-profile-row >> nth=-1' (it sorts last) or by its badge.
  • Model lookups (F13.acp-model-discovery) spawn the provider CLI on the agent-server machine through npx with the profile's allowed secrets; they send no prompt. A failed lookup is retried each time the preset is picked again; a successful one is cached for about five minutes per preset and secret set.
  • Switch rows are sr-only checkboxes inside labels (keyboard reachable): click the label text ('testid=agent-settings-tool-list >> text=<tool>', 'testid=agent-settings-mcp-list >> text=<name>') and read .checked with browser eval. A bare label selector segment is parsed as label=, not CSS.
  • The Default badge, the Set as active item and the Switched to profile toast all refer to the same active pointer; the wording differs from the LLM page (Set as default).
  • The OpenHands row's grey text is the LLM profile name, not the kind. A fresh run's default shows default there although no LLM profile of that name exists until it is edited (F13.stale-llm-ref).
  • Local seeded OpenHands default launches through global agent settings so the Skills selection applies; its per-profile fields do not apply on that path. Named profiles use their saved settings. Home launches follow the LLM pill. On a backend advertising per-launch LLM override, a named profile pinned to another LLM keeps its tool, concurrency and MCP settings; read launched_agent_profile and agent on the conversation (F13.llm-pill-precedence). Only older backends without that capability use the legacy settings fallback when the profile and pill differ.
  • The ACP auth probe runs the provider CLI on the agent-server machine. If that machine has a Claude CLI login, Claude Code can show "already signed in". Use only an explicitly approved account for authenticated ACP checks.
  • ACP credentials are global secrets, not profile fields: cancelling the editor saves nothing, but saving any ACP profile with a typed credential writes it to Settings → Secrets (F14) for every conversation.
  • Choose secrets on an ACP profile also selects provider secret names that are not saved (CODEX_AUTH_JSON, OPENAI_BASE_URL); they show as switches without descriptions.
  • The empty state (agent-profiles-empty) and the load-error state (agent-profiles-load-error) are not reachable on a local backend: deleting the last profile re-seeds default, and with the agent-server stopped the app swaps to Manage backends after a reload, or keeps the cached list without one.
  • The refused-launch toast text comes from the Agent Server and changes between versions (MCP server(s) not configured: <name> on 1.53.0): assert that one toast names the server and the prompt stays, not the exact wording.
  • Opening a row menu focuses its first enabled item. Arrows skip disabled Set as active; Escape closes the menu and returns focus to its trigger, while Tab closes it and continues normal tab order.
  • The Add agent editor has its own agent description. Parallel tool calls label and description are translated.
  • Known issue #16905: An enabled built-in OpenHands skill can be absent from a new Claude Code ACP session. The enabled/disabled instruction-delivery check needs an approved authenticated Claude Code account, its ACP subprocess and the configured Code Review automation; no provider session is authorized here. (F13.acp-conversation).
  • Known issue #16296: Claude Code credential fields remain visible below the already-connected banner, leading users to think keys are required. The connected-state check needs an approved existing Claude CLI login on the agent-server host; a dummy credential does not prove that signed-in state. (F13.acp-credentials).
  • Known issue #17051: Codex ACP can work in New Chat while Issue-to-PR automation fails with missing OpenAI API credentials. Reproduction needs an approved Codex subscription login plus a configured Issue-to-PR automation and its repository credentials; the report does not identify the saved automation/profile settings. (F13.acp-conversation).
  • Known issue OpenHands/software-agent-sdk#5167: On a headless Docker host an already usable Codex ChatGPT login can still time out during ACP authenticate. Reproduction needs an approved ChatGPT login in the reported headless/read-only-auth-file deployment and its adapter; a desktop or bogus login cannot establish this conditional trigger. (F13.acp-conversation).
  • Known issue OpenHands/software-agent-sdk#5995: Claude Code ACP first turns with claude-opus-5-5 can fail because the pinned adapter bundles an older Claude Code. Blocked: needs an approved Claude login/API key entitled to Opus5.5 and a real first turn; current SDK1.55 still pins the reported adapter0.63.0. No authenticated turn was attempted. (F13.acp-conversation, F13.acp-model-discovery).
  • Known issue OpenHands/software-agent-sdk#5899: The browser tool can be reported unusable when genuine Chromium exists only under PLAYWRIGHT_BROWSERS_PATH. Blocked on this host: it has standard user Chrome. The distinguishing fixture needs a host/container with no standard/default-cache/PATH browser, plus a genuine custom-cache Chromium; never remove the user browser to create it. (F13.openhands-options; operational browser path F27).

F14 Secrets

12 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Settings nav test ids embed the route, slashes included: sidebar-settings-/settings/secrets. Quote the selector as written.
  • The value editor is a plain textarea, not a password field: use dummy values only, and never ask the agent to print a real secret.
  • Edit forms intentionally show an empty value; that is not data loss.
  • The invalid-name check is native browser validation. It shows a tooltip that screenshots do not capture, and it leaves any earlier Secret already exists paragraph on screen. Assert with validationMessage and the API, not the paragraph.
  • The model is the weak link in the agent checks, not the product: asked to print a secret's length or last characters, deepseek-flash replies "I can't run that one" and runs nothing, and even for the equality check it may refuse to repeat True in its reply. Use the equality prompt and read the terminal ObservationEvent; conversation events truncates message text to about 160 characters.
  • The conversation Overview has no Secrets entry point: its "..." menu (conversation-overview-ellipsis) offers Workspace and Git sections only.
  • There is no empty state to reach safely: secrets-empty renders only with zero secrets, which means deleting the launcher-seeded key (see below).
  • Secrets saved in Settings reach only conversations started after the save. Reuse an older conversation and the variable is missing. Only request_secret (F14.agent-request) attaches a secret to a running conversation, and only on a local backend.
  • request_secret is registered on every new OpenHands-agent conversation this frontend launches (not on ACP agents or older conversations) and appears in Show Agent Tools & Metadata (see F07.agent-tools-modal). The agent learns the outcome from a hidden [secret-request] ... user message; read it with conversation events <id> --kinds MessageEvent, not from the chat.
  • At 390 px the name column truncates long names (OPEN…); use browser text on the row rather than the screenshot to read a name.
  • OPENHANDS_AUTOMATION_API_KEY is seeded by the launcher for automations; deleting it breaks F18/F19 runs in the same stack.
  • Known issue #17710: On the Cloud backend, deleting a secret reportedly adds a suffixed duplicate and leaves the original. Reproduction needs a disposable writable OpenHands Cloud account at the affected service; Local secret CRUD exercises a different transport. (F14.delete, F14.edit).

F15 Condenser, agent context and verification settings

23 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Switches are sr-only checkboxes (keyboard reachable): toggle by clicking the label text scoped to the page, read .checked with browser eval.
  • text=Enable Critic is a case-insensitive substring match and hits the help paragraph Enable critic evaluation for the agent. first, so the click does nothing. Quote it: text="Enable Critic" (same for text="Enable Iterative Refinement").
  • The conversation-owned fields have no verification. prefix: sdk-settings-confirmation_mode, sdk-settings-security_analyzer. Critic fields keep it (sdk-settings-verification.critic_enabled).
  • Saving is tier-scoped: Basic or Advanced saves reset every field the tier hides to its schema default (F15.view-scoped-save). Make all edits on All when other non-default values must survive.
  • The page opens on the least detailed tier that shows every stored override (any minor override → All). Do not assume Basic after a reload.
  • Condenser Kind None (no_op) still shows the summarizer fields with defaults, although the stored condenser no longer has them, and edits to them are accepted (Settings saved) but dropped while None is stored. #18049 covers hiding fields that do not apply to the selected kind.
  • A server-side validation error is retried three times (about 1.5 s of Saving...) before the toast, and the toast only says Settings validation failed without naming the field (#17932).
  • Condenser Kind is served by the Agent Server's settings schema as a user field. Canvas shows its own choice labels and help text rather than raw llm_summarizing/no_op values or reStructuredText. The dropdown (F15.condenser-kind) remains a user setting so additional kinds can be selected. OpenHands/software-agent-sdk#5495, the schema's developer-facing description, remains an upstream issue.
  • Toasts close after a few seconds; read them right after the click, or assert the stored value with api GET /api/settings.
  • Under confirmation mode the first confirmed command sometimes returned The terminal session was reset because the underlying tmux server/session disappeared... while several runs shared one tmux server (OpenHands/OpenHands#17946); launch now isolates them, so treat a reset as a finding. The agent then asks to run it again; a confirmed command took up to two minutes to produce its observation. Confirm again and wait with conversation wait, not a fixed sleep.
  • The confirmation buttons have test ids (action-confirm-button, action-reject-button) but their accessible names are Confirm action / Reject action, not the visible Continue / Cancel: role=button[name*="Continue"] finds nothing.
  • Basic-tier saves on Verification reset Security Analyzer to llm (it is an Advanced field). That is a convenient restore, and a trap if you meant to keep None.
  • The memory instructions are only visible through the conversation's first event; conversation events truncates text, so read api GET /api/conversations/<id>/events/search?limit=1 and search the JSON.
  • A checkout without #18035 shows the condenser minor fields with the schema's English text (Max tokens, Keep first, raw choice values) and logs 14 [i18n] Missing translation warnings on the All tab.
  • The dropdowns' clear (×) button has no accessible name and, in this build, clicking it through role=button >> nth=2 left the value unchanged.
  • After every successful save the selected tab is recomputed from the stored values (F15.tier-after-save), so a follow-up fill on an All-only field can time out because the page silently moved to Basic. Re-click sdk-section-all-toggle before each further edit.
  • Pressing Enter in a field does not save (no toast, API unchanged, Save stays enabled); only the Save Changes button submits.
  • Fields revealed by a parent switch do not exist while the switch is off: fill on critic_threshold with Iterative Refinement off waits for a missing element.
  • Rejecting a pending action (Cancel), the lock chip above the composer and the ⌘↩ / ⇧⌘⌫ shortcuts belong to F06.confirmation-mode and F06.confirmation-shortcuts; this family only proves that the settings turn the prompt on.
  • browser count 'role=status' also counts the backend status dot (backend-status-dot); use browser toasts for toast texts.

F16 Application settings

20 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Two save models on one form: theme, checklist and voice input apply instantly and ignore Save; the rest is dropped silently if you navigate away before Save (filling git-user-name-input, then clicking sidebar-settings-/settings/secrets and back, shows the stored openhands again; browser dialogs is empty). Assert the instant ones without clicking Save, and never expect Save to enable for them.
  • The success toast uses the language that was active when you clicked Save, not the one you saved. In F16.language the French Save toasts Settings saved, the Arabic Save toasts Paramètres enregistrés, and the English restore toasts تم حفظ الإعدادات, so wait-text 'Settings saved' times out on the last two. Assert with browser text 'testid=submit-button' after a reload, or read the toast with browser toasts.
  • Some chrome strings stay English after a language switch: the sidebar Automate label in every language (the automation interface supplies it, not the translation file), and most of the command menu's own texts (placeholder Search actions, pages, and settings..., the search box's Search commands name, the group names and the item names) in French and 11 other languages. Only the Model Router and Agent Context items are translated (Routeur de modèles and Contexte de l'agent in French), because they reuse the settings navigation's titles. Do not treat the English ones as a language switch failure; the sidebar trigger's Search commands is translated.
  • In Arabic (dir="rtl") a switch that is on draws its thumb outside its track (on show-getting-started-checklist-switch the track spans x 778–818 and the thumb 822–838). Assert switch state with .checked, not from a screenshot.
  • The switches are visually hidden native checkboxes: Use the label as the pointer target. Click the label text scoped to testid=app-settings-screen, or use Tab/Space, and read .checked through eval.
  • role=option[name^=...] is not supported by the selector parser; use role=option >> has-text=<profile> for profile options whose names include the model.
  • conversation start leaves the browser on the conversation page, which also has a testid=submit-button; goto /settings/app before the next settings step or clicks hit the composer.
  • Which profile actually generated a title is not visible in the UI, the conversation API or the stack logs; F16.title-model proves the preference is stored and a title appears, not which model wrote it.
  • onboard --skip stores analytics off, so a fresh run starts with the switch unchecked even though the code defaults to on when the preference is missing.
  • browser storage lists localStorage, and browser storage --session lists sessionStorage, where the voice-input key lives. With --values that key prints as <redacted>, so the recipe reads its dummy value with eval. The key disappears in a new tab or browser session by design.
  • Deleting a profile through the LLM settings UI clears a matching title preference itself; only a profile removed outside the UI leaves a stale title_llm_profile that this page shows as Automatic.
  • The Title generation model combobox has a clear (×) button with no accessible name (- button in the snapshot, visible as × at phone width). At desktop width it only takes clicks after browser hover on the input (F16.title-model-clear); otherwise use the Automatic option.
  • browser eval wraps its argument as an expression: statements separated by ; fail with SyntaxError: Unexpected token ';'; wrap them in (() => { ...; return x; })().
  • With service stop agent-server, a browser reload of /settings/app shows the backend-unavailable screen (agent-server-onboarding-screen, Manage backends), not the form: stop the service only after the page is loaded, and restart afterwards.
  • Known product bugs (repro candidates): git identity never reaches the agent (#17899); a failed save discards the user's edits (F16.save-states) (#17927).
  • The harness builds with VITE_DO_NOT_TRACK=1, so PostHog never starts: browser network --filter 'posthog|z\.openhands\.dev' reports total 0, also after analytics consent is granted (#18086). A browser-only Do Not Track setting is outside this recipe. Consent reversal requires the explicit non-DNT build and loopback-only analytics configuration described above, served with launch --build never (F16.analytics).
  • The title model's clear button has no test id: the recipe's xpath relies on DOM nesting to skip the unnamed Show suggestions button. If both ever match, Playwright's strict mode fails the click instead of pressing the wrong one; report it as harness drift.

F17 Customize hub and MCP servers

31 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Server errors on MCP mutations show one toast with the server's message, or An error occurred when the body has none. A custom server's Save Server or Add Server action whose pre-save connection test fails keeps the editor open. To check backend loss, stop the owned Agent Server with the editor open, click Save, then restore it with control-openhands restart; background settings polls can add separate error toasts, so count only the toast caused by the click. A failed add, edit or delete is retried three times (withRetry in settings-service.api.ts, 0.5 s and 1 s apart) before its single toast; that retry behavior is open #17932.
  • Desktop /customize is a client-side redirect: browser url may still read /customize for a moment; use wait-url '/mcp(\?|$)'.
  • From 768 to 1023 px the sidebar rail stays and the phone top bar is gone. The compact Customize selector above each detail page provides sibling navigation; the rail's Customize link (sidebar-skills-link, unscoped at this width) still returns to the hub.
  • On a phone the drawer and the hidden desktop rail both render sidebar-skills-link; scope it with testid=sidebar-mobile-drawer >> ... or the click fails with a strict-mode error, even though browser testids lists it once.
  • Editor dropdowns are comboboxes: click server-type-dropdown or auth-mode-dropdown, then role=option[name="..."]. Server types read SSE, STDIO and SHTTP (not "Streamable HTTP"); auth modes read None, Bearer token, Header and OAuth. The type dropdown exists only when adding.
  • The timeout field is type="number": browser fill refuses letters, so Timeout must be a valid number cannot be reached from the UI.
  • A red test message stays on screen when a later validation error appears, so css=p.text-feedback-error can match two paragraphs; take >> nth=0 (the validation error comes first).
  • Install-modal required fields use native browser validation (a tooltip, no paragraph): assert validationMessage.
  • The installed and library toggles are role=switch buttons: read aria-checked, not aria-pressed.
  • MCP stdio servers start with a minimal environment. Behind a TLS-intercepting proxy, without trust settings in the run's private HOME, uvx catalog installs fail with Connection closed (<run>/private/stack.log shows UnknownIssuer) and npx ones with Connection timed out. That is the sandbox, not the product. launch now seeds the trust files from SSL_CERT_FILE/NODE_EXTRA_CA_CERTS, so a fresh run installs fine and F17.install-error must move them aside; once a package is in the run's uv cache it installs even without them.
  • An installed card's health row swallows clicks (so the probe button does not open the editor). On a card without a catalog description (qa_broken) that row is the card's centre, so a plain card click opens nothing; a catalog description moves it down. Open the editor by the card's name or mcp-server-detail-<id> line, or focus the card and press Enter.
  • conversation start leaves the browser on the conversation page; every MCP-page command after it needs browser goto /mcp first, or the per-card selectors silently wait on a page that has none.
  • @modelcontextprotocol/server-everything sse printed Server is running here but never answered on its port; use the streamableHttp mode for the remote-server fixture.
  • A server without a catalog credential probe reads Reachable — credentials not verified, never Connected — credentials verified (N tool(s)), even without credentials. Health after a save is seeded from the pre-save test and is lost on reload (Not checked yet).
  • Adding a custom server shows no toast; only catalog installs and deletes toast. Assert the card after reload.
  • Catalog installs are add-only: a second Time install becomes time_1. STDIO servers match the catalog by command plus leading args rather than name, so time_1 and a custom server running a catalog command under its own name (qa_time) retain the description, badge and search keywords. A server that only borrows a catalog name does not (F17.install-stdio, F17.custom-add). Twelve provider-OAuth-only library entries, such as Sentry, HubSpot and Cloudflare OAuth, open an empty modal whose Install does nothing (F17.install-unsupported, #17922).
  • The section filter's trigger and menu are named Filter MCP servers.
  • Header and environment-variable format errors have distinct copy. F17.custom-validation expects Headers must follow KEY=value format for headers and Environment variables must follow KEY=value format for env lines. Feedback uses readable theme classes text-feedback-error and text-feedback-success.
  • A catalog stdio server runs with the Agent Server's minimal environment: behind a TLS-intercepting proxy its own HTTPS calls fail (fetch failed) even when npx could download it, and the install modal has no field for NODE_EXTRA_CA_CERTS; use the custom editor with the same command to see the provider's real answer (F17.credential-probe). The Credential check failed: fetch failed wording itself is #18161: the Slack interpreter turns any is_error tool result into a credential failure, though the server connected and listed its tools.
  • GitHub's hosted MCP endpoint answers an invalid token with HTTP 401 at connect, before tools are listed, so its get_me probe never runs and the modal shows the connection wording (Connection failed: HTTPStatusError … 401 Unauthorized), not Credential check failed; filed as OpenHands/software-agent-sdk#5607, since the test endpoint reports the 401 as error_kind unknown.
  • The shell-gated qa_vault fixture exits before the MCP handshake, which the Agent Server reports either at once as Connection failed: McpError: Connection closed or only at its deadline as Connection timed out. Check the URL and try again.; neither is the Could not start the server command wording, which needs a command that cannot be spawned at all. Wait on text=Connection, which matches both and not Connected — ….
  • browser network --bodies shows what the page sent, never a secret: a value under a key that names a credential (key, token, secret, auth, pass, session, sig, credential) and every value of an env or headers map becomes <redacted N chars> (N the real length), and a URL in any other string (a server's url) keeps its host and path but loses its userinfo and the values of its secret-named query parameters (https://<redacted 2 chars>:<redacted 8 chars>@host/mcp?api_key=<redacted 17 chars>), while the settings API's ********** placeholder is kept as is, so a row tells "placeholder sent" from "a value sent". The MCP editor sends neither for an untouched secret: Test connection swaps the placeholder for the stored encrypted value (a GET /api/settings in encrypted mode first; Fernet tokens, so 120 chars for a 19-char token, 140 for 40, 268 for 142) and Save leaves env out of its sparse PATCH altogether (F17.stored-secret-reuse). Without --bodies no row carries a body field, and only app-origin writes keep one.
  • A browser verb's client call waits 120 s, or 10 s longer than the verb's own --timeout or --observe-ms when that is longer, so browser wait 'testid=mcp-install-modal-error' --timeout 150000 runs its full deadline and a miss ends with Playwright's timeout and a failure screenshot (before this, every call was aborted at 120 s with TimeoutError: The operation was aborted due to timeout while the page kept going). For a first Slack install use a plain click, then that wait; --observe-ms is for a few transient states, not a minute-long outcome.
  • Known issue #17941: turning off an enabled card toggle can drop the click when the pointer moves in and presses at once (F17.enable-disable).
  • Known issue #15421: A configured Linear MCP can be ignored while an explicitly disabled Linear skill is used instead. The new-chat and automation paths need a working authorized Linear MCP connection, a concrete issue ID and an approved model; the report's automation ZIP is also needed to replay its exact automation settings. (F17.agent-uses-server).
  • Known issue #15430: An OAuth MCP callback in Docker can redirect to an unexposed dynamic localhost port. Completing the callback needs a Docker stack and an approved OAuth MCP provider/account (the report uses GitLab); a local unauthenticated Test connection cannot reach the redirect. (F17.install-oauth).
  • Known issue OpenHands/software-agent-sdk#5375: A successful GitLab OAuth login can be followed by an invalid localhost redirect URI on the first ticket-to-code-change MCP action. Reproduction needs the authorized self-hosted GitLab MCP endpoint/account and the ticket-to-code-change plugin/workflow; the reported endpoint is not a public fixture. (F17.install-oauth, F17.agent-uses-server).

F18 Skills catalog

22 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • sidebar-skills-link is labelled Customize and lands on /mcp on desktop (/customize redirects); Skills is one more click. Test ids embed the route: quote 'testid=sidebar-extensions-/skills'.
  • A browser click on an enabled card toggle is lost when the pointer was not already over it: pointerenter swaps the checkmark icon for the X icon between mousedown and mouseup, so Chrome fires no click and no PATCH /api/settings goes out (#17941). browser hover the toggle first (or click twice). Turning a skill on is not affected. Recorded as fail on F18.toggle.
  • The card toggle and the facet rows are role=switch/role=checkbox buttons: read aria-checked. The modal switch is an sr-only checkbox (keyboard reachable): click the row text and read .checked with browser eval.
  • Typing writes ?q= with replace after a 300 ms debounce, so read browser url a second later; facet clicks push a history entry immediately.
  • Facet counts are disjunctive: a row counts skills matching the other groups' selections, so Disabled reads 0 (and is disabled) while Recommended is checked.
  • Groups with fewer than two values are hidden (Source and Type on a fresh run) unless the URL selects one of their values.
  • The run's HOME lives under /tmp, so a skill in ~/.agents/skills is filed under Source This project (source=project), not Personal; with a real /home/<user> or /Users/<user> home it would be Personal.
  • conversation events rows show a user MessageEvent's activated skills as skills: [...] (since #18162). api GET '/api/conversations/<id>/events/search?limit=5' --max-bytes 2000000 shows the raw activated_skills, but only when the user row is among those five events. On 2026-10-08 it was not: the first five were the system prompt and state updates.
  • Skill lists are cached for 10 minutes: browser reload (or goto) after the agent creates a skill file, or the new card does not appear.
  • navigator.clipboard.readText() through browser eval hangs (no clipboard permission in the page); read what a copy button wrote with control-openhands browser clipboard and assert the button's aria-label too. The app runs on 127.0.0.1 (a secure context), so the unguarded clipboard.writeText calls cannot be tested for insecure-origin failures here.
  • With the Agent Server stopped, reloading /skills shows the "Manage backends / Disconnected" screen instead of the page: load the page first, then stop the service. After the failed save the card keeps showing the new state until a reload (#17941).
  • Escape from the modal's focused +N overflow trigger closes only the popover and restores trigger focus; the next Escape closes the detail modal. The card popover uses the same Escape behavior.
  • The Add skill example, its Copy button and the add-skill Use skill message share the .../skills/code-review path.
  • Skill changes apply to new conversations only; an open conversation keeps the skills it started with.
  • conversation events --grep searches whole events, the user's message text included: a trigger that contains the skill name (qa-gone-ping for qa-gone) matches the user MessageEvent even when the skill is not loaded. Give fixture skills a trigger that does not contain their name, or grep for <name>qa-gone</name>.
  • fixture skill --repo writes the project SKILL.md and records it in the repo only with --commit (committed.sha; a re-run reports unchanged); a New Worktree conversation checks out the repo's commits, so pass --commit. Workspace skills are loaded per conversation and never appear on /skills.
  • Without an LLM key the skill set is still observable: the SystemPromptEvent (available skills), the user MessageEvent (activated_skills) and the chat's Skill Ready row (generic-event-message-title; Expand lists the skills under Triggered Skill Knowledge:) all exist before the first model call, after which the conversation ends in error.
  • Known issue #16298: A Slack-monitor setup can leave the agent without the expected skill. The report omits the concrete monitor setup and loaded skill name/version; reproducing the runtime also needs an approved model and a configured Slack integration. Listing the bundled catalog alone cannot settle it. (F18.skill-in-chat).
  • Known issue #15458: The global Skills page can list AGENTS.md files from cloned automation-run worktrees. The report says the discovery trigger is unknown; replay needs the affected automation workspace/discovery-root layout or captured request parameters, rather than only a normal project-skill fixture. (F18.page, F18.project-skill).

F19 Plugins and plugin launch

25 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • The card switch swaps its icon on hover. A plain browser click that moves the pointer onto it from elsewhere (for example right after clicking a filter segment) is often swallowed (#17941): no PATCH /api/plugins/installed/<name> shows in browser network. Run browser hover on the switch first, then click, and assert aria-checked and the API, not the click result.
  • Installed plugins lose their source and their enabled state, intermittently (OpenHands/software-agent-sdk#5496). The Agent Server rewrites <run>/private/plugins/installed/.installed.json with a plain truncate-and-write on every load (SDK InstallationMetadata.save_to_dir), so a concurrent GET /api/plugins/installed (page loads fire several) can read an empty file: it logs Failed to load installed extension metadata … Invalid JSON: EOF while parsing and Discovered untracked extension, then saves every plugin with "source": "local", "enabled": true and a fresh installed_at. It hit catalog installs in most trials and a disabled plugin on 2 of 3 page revisits; GitHub installs kept their source in this run. Once it hits, the card shows local, Update fails with a 500, Start Conversation builds an unusable /launch link, and a disabled plugin silently auto-loads again. Prove the launch path from a card that is not installed, and re-read the API after every navigation.
  • A failed plugin install, update or uninstall shows one toast with the server's reason (enable uses the same handler; its failure is not driven here). A response with no reason in it, such as the ingress's 502 while the Agent Server is stopped, shows the generic An error occurred; read the cause in the run's private/stack.log.
  • Installing a plugin whose name is already installed points the user to Update or Uninstall, as Apps do (F20.install-duplicate): with the catalog magic-test installed, Add plugin with github:OpenHands/extensions and Path plugins/magic-test answers 409 (browser network --filter 'plugins/install') and browser toasts --history holds one toast, This plugin is already installed. Open its card to Update or Uninstall it., instead of the server's force=true hint; the modal stays open with the typed values (browser screenshot --feature F19.add-plugin-error --name duplicate-409). Other install errors still toast the server's reason (F19.add-plugin-error). The 409 adds one http-error and one console.error to browser errors --app-only.
  • Uninstall acts immediately, with no confirmation (MCP server delete and App uninstall confirm).
  • /launch with a message starts the agent as soon as Start Conversation is clicked. Prefer links without a message and send alakazam yourself; a city-weather launch with a message makes the agent call an external weather API (stop it with testid=stop-button).
  • Edited launch parameters cannot be observed after creation: the conversation's client metadata (localStorage['openhands-agent-server-conversation-metadata']) strips them and the Agent Server does not return a conversation's plugins. Proving delivery needs request-body capture (harness gap).
  • conversation start and launches leave the browser on a conversation page, which has its own chat-input; browser goto /plugins before the next card step.
  • Build deep links with URL-encoded base64 (%2B, %2F, %3D): a raw + decodes to a space and gives the invalid-format error. Non-ASCII text must not be used in payloads (atob decodes bytes as Latin-1).
  • The page loads three queries (marketplace, installed, local) behind skeleton rows; wait for testid=plugins-toolbar before counting cards. With no plugins at all the page shows No plugins found. (plugins-empty), which the bundled catalog makes unreachable; on a Cloud backend Add plugin and card actions are disabled (not driven: no Cloud account).
  • Local plugins under ~/.agents/plugins appear on the next page load without a restart; the Agent Server's HOME is <run>/private/home.
  • Known issue OpenHands/software-agent-sdk#5985: Update can re-enable a disabled plugin when an installed-list request overlaps the force-reinstall directory swap. A live check needs an owned disabled plugin and a real concurrent list that lands in that window; the report's monkeypatched copytree schedule is not Canvas live proof. No overlap has been proved in this pass. (F19.refresh, F19.enable-toggle).

F20 Canvas apps

24 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • The card's refresh button (canvas-extension-refresh-<name>) is labelled Update, not "Refresh".
  • The trust confirmation has no title and reuses the shared confirmation-modal; its body repeats the amber notice. Space on the disabled app's Enable switch focuses Cancel. Tab and Shift+Tab wrap between Cancel and Enable trusted app; Escape and Cancel close it and restore focus to the still-disabled Enable switch.
  • nav_label is dropped by the Agent Server: GET /api/canvas-extensions/installed returns pages with only id, title and path (Agent Server 1.50.1), so the rail shows the page title (OpenHands/software-agent-sdk#5501). Keep F20.rail-label failing until the backend returns it.
  • Install and update errors show the server's reason as sent, even when it says Failed to fetch extension from <url> (an unreachable Git source). A response with no reason in it, such as the ingress's 502 while the Agent Server is stopped, shows the generic An error occurred. Only a request that gets no response at all shows Disconnected (check URL or network)…; no control-openhands verb takes the ingress offline, so that state is not driven here.
  • The page states (empty, error, unsupported) and the /extensions/... unavailable card have no test ids; assert on text with browser snapshot.
  • The Agent Server fetches Git sources with a shallow clone and appends .git to the repo URL: a test Git server must speak smart HTTP and answer both qa-repo and qa-repo.git.
  • The Git bullets read the public upstream repo, so they depend on its main still holding the identical fixture; the precondition's diff -r guards that. A Git install or Update clones in about 0.4 to 2 s here (Updates measured about 0.4 to 1.5 s on 2026-10-06), long enough for the busy-lock checks when they run in the same shell line as the click and read both controls in one eval; on a slower network raise the wait timeouts, not the expectations.
  • A duplicate install answers 409 with the Agent Server's API hint (Canvas extension already installed. Use force=true to overwrite.); the form has no force option, so the toast replaces it with the Update/Uninstall advice. Plugins give the same advice since #18110 (F19 Gotchas).
  • Busy states with a local-path source last about 50 ms: --observe catches Installing…, but checking that controls are disabled during Update needs a Git source (an upstream Update lasts about 0.4 to 2 s).
  • control-openhands launch passes the launcher only a fixed list of environment variables (path, locale, proxy) plus its own flags: OH_AGENT_SERVER_VERSION=<old> in the environment is ignored (the run still gets the pinned server); use --sdk-ref v<version>. Any server older than 1.47.0 is gated by Canvas before /apps renders.
  • Stopping the Agent Server makes the next full page load show the Manage backends gate; always restart and doctor afterwards.
  • A trailing browser errors --app-only sweep shows a 400 per failed install by design; count only other errors.
  • App backend view unavailable on the demo page is the expected text while GET /server_info lists no canvas_app_backend_bridge_v1 capability (Agent Server 1.53.0); it does not mean the app failed to load. Cloud backends and the no-backend state never get a backend view either.
  • Known issue OpenHands/software-agent-sdk#5648 (F20.app-backend-view): app-backend WebSocket subprotocols are reported dropped. Recheck on a Linux Agent Server with configured public ingress and reviewed platform-matching backend artifacts, using a genuine admitted app-backend session and subprotocol negotiation. SDK 1.55.0 supports only Linux app-backend artifacts, so a native macOS server cannot drive this branch. A default demo without the bridge capability cannot exercise the proxied WebSocket path.
  • Known issue OpenHands/software-agent-sdk#5935 (F20.app-backend-view): an explicit OH_APP_BACKEND_PUBLIC_URL_IS_NONE=1 does not clear a bridge URL set in the Agent Server JSON configuration. Canvas can therefore be offered an app-backend view at the unwanted configured ingress. Recheck an owned bridge-capable server with a JSON public URL and that environment-null override, then inspect /server_info and the real app page; the default launcher has neither conflicting configuration source.
  • Known issue OpenHands/software-agent-sdk#5914 (F20.app-backend-view): app-backend HTTP responses can carry both the backend's and the Agent Server's Date and Server headers, producing an invalid joined date and disclosing the backend stack. Recheck a reviewed HTTP app backend on a supported Linux Agent Server that sends these headers through a real admitted app-backend session. A default demo page without the bridge capability or a direct backend request cannot establish the proxied response.

F21 Automations dashboard and actions

35 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Both sub-page navs are always in the DOM (one hidden by CSS). Unscoped testid=automations-navigation-templates is a strict-mode violation; scope it with testid=automations-navbar-desktop >> or testid=automations-navbar-mobile >> .
  • The pin toggle is invisible until the Automate item is hovered on a fine pointer: browser hover 'testid=sidebar-automations-link' before clicking it. The pin is stored per backend and org in localStorage (oh:pinned-home-route:<backend>:<org>); unpin before leaving the run.
  • The Filters popover's status and trigger choices do not apply to the Drafts group: with status Active and trigger Event-driven, a cron draft stays listed and the filtered empty state does not show (search does filter drafts). Intent undocumented in #17242.
  • The setup page's success toast (Automation updated) covers its toolbar's Test, Save and Close and does not time out while the pointer rests on it; a click there fails with "subtree intercepts pointer events". Move the pointer away (browser hover on a form field), wait about 5 s, then click.
  • testid=dropdown-trigger is reused by the backend selector, the Filters button and every inner dropdown. Always scope it (testid=automations-filters >> testid=dropdown-trigger >> nth=0 for the Filters button, testid=automations-filter-status >> testid=dropdown-trigger for a field).
  • The Filters popover stays open while choices change and closes only on a click outside it or on the Filters button; Escape does not close it. Open, it covers the right part of the list (in list view the first rows' Play buttons): close it with a click on role=heading[name="Dashboard"] before clicking row or card controls.
  • A card whose automation was deleted elsewhere stays on the dashboard after its Run now fails with Automation not found. Expected: the card drops out once the dispatch returns 404. Known failure: nothing refetches the list after the failed dispatch, so the card stays until a reload or another list refetch (still there 2 minutes later) (#18062).
  • The delete confirmation has no test ids; scope it as the named dialog role=dialog[name="Delete automation"]. It opens with focus on Cancel.
  • Delete closes the dialog at once and shows no success toast; confirm with a reload. Turn off on the dashboard never asks for confirmation (home cards do).
  • The kebab menu has no test ids; role=list >> has-text=Export is the reliable scope. A card's own Run now has the same accessible name as the kebab item, so scope Play to the card.
  • Imported automations are always disabled (Import automation says so), and Run now is disabled until you Turn on. Use the yearly cron 0 0 1 1 * or an event trigger so an enabled fixture never fires by itself, and turn it back off.
  • Run now uses the active LLM profile when the automation's model is Active profile. A successful run creates a conversation titled <automation name> — <UTC timestamp> that stays after the automation is deleted. Delete it separately (control-openhands conversation list, conversations family) to leave the run clean.
  • A timeout of 1 s does not fail fast: the run sat in Installing dependencies and failed with Timed out: command timed out or was killed about 3-4 minutes after dispatch. Budget for that when you need a failing run for Needs attention.
  • The Automations tile counts only loaded pages: with 51 automations it reads 50 until Load more is clicked, although the API total is 51.
  • The row health dot and label reflect the latest run only; a disabled automation with no runs reads No activity yet, not Disabled.
  • In grid view a cron automation without schedule_human shows the pill cron, while list view shows the raw expression (0 0 1 1 *).
  • The view mode persists in localStorage (openhands-automations-view), and the toggle is disabled when the org has no automations. Switch back to Grid before deleting the last automation, or the next run starts in List.
  • Filters, search and sort are page state only: a reload resets them.
  • service stop automation stops only this run's automation service; restart brings the whole stack back with the same state and the browser on the same page. Run control-openhands doctor afterwards.
  • The Automation dispatched toast lasts only a few seconds: run browser wait-text 'Automation dispatched' right after the click, not after a long --observe window.
  • browser screenshot --full-page at phone width does not capture below the fold, because the page scrolls inside the main pane; screenshot sections or use bbox.
  • Known issue OpenHands/automation#246 (F21.backend-unavailable, F21.list-error): The report says only that local automation returns 404. It omits the URL, UI action, release and launcher/service setup, so a normal successful dashboard visit would not reproduce or resolve its unknown branch. Ask for those exact prerequisites before writing a failing recipe.
  • Known issue OpenHands/automation#347 (F21.card-grid, F21.overview-tiles): per-automation run-summary reads lack an aggregate contract, and the report describes a 30 s shared-resource stall with HTTP 500 plus SQLite pool settings that do not apply. A failing recipe needs the original database/lock workload and redacted service-log/HAR timing evidence, which the report withholds. Successful summary reads under the Canvas request throttle do not establish those backend contracts.

F22 Creating automations: templates, setup and import

29 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Expected: a completed draft test updates the open Test runs row to Successful automatically. Known failure (reproduced again 2026-10-13 on a desktop prompt draft: run COMPLETED at 08:13:39, row still Pending after a further 45 s wait; verified 2026-10-10, fresh Local stacks, Agent Server 1.54.0/automation 1.19.3): callback-only script tests and genuine DeepSeek prompt/plugin tests report COMPLETED while the open row stays Pending. The desktop prompt stayed Pending through a 45-second wait; the phone plugin capture was more than 201 seconds after completion. Reload and View test runs recover Successful (#18263). Ordinary final-automation Run now polling passes; reload recovery does not waive the draft-Test failure.
  • The Save toast can cover the setup toolbar Test button; use the lower automation-setup-draft-test and verify the actual run outcome. The toast does not time out while the pointer rests on it, so move the pointer away (browser hover on a form field) before clicking the toolbar again.
  • The setup page's docked composer keeps no draft: it opens empty even when the Home composer has an unsent draft (sessionStorage oh:home-prompt-draft), a prompt sent from it never becomes the Home draft, and leaving it keeps an existing Home draft. Unsent text typed there is lost when the user leaves the page.
  • In the desktop split view (form beside the conversation) the Frequency strip scrolls horizontally inside the narrower form and the selected Custom button sits off-screen; scroll it into view before reading it.
  • Dashboard draft Delete asks in the named dialog role=dialog[name="Delete draft?"] (F21.saved-drafts). Deleting a draft also removes its materialized test automation.
  • A card click and a deep link are different paths: from the grid, GitHub/Slack-only responders (qa-changes, github-agents-md-maintainer, slack-channel-monitor, …) first show the responder choice and then the GitHub/Slack install dialog, so their setup form is reachable locally only by URL (/automations/new/<id>).
  • A card's N MCPs to connect before launch pill counts optional integrations too: Custom automation shows 1 MCPs to connect before launch for GitHub, yet opens its setup at once because GitHub is optional there (#17959).
  • The featured-section copy says picking a card opens "a pre-filled conversation"; most featured cards open a setup form instead.
  • The setup dialog's comboboxes (Action, Agent profile, Timezone, LLM profile) are autocomplete inputs: click the field and pick the option in the same breath, scoped as role=listbox >> role=option[name="…"]. A browser snapshot between the two closes the listbox (reproduced twice), and the option click then times out after 30 s; click the field again.
  • Service preflight runs 400 ms after a blur, not on every keystroke; browser press Tab then browser wait for the -error element.
  • Confirm and create creates the automation enabled; only imports arrive disabled. Use a yearly cron (0 0 1 1 *) so nothing fires during the run.
  • The Daily news digest entry names its automation Daily news digest (no QA_ prefix); remember it in cleanup.
  • The Agent profile combobox exists only while the chosen action creates through /v1 (Upload tarball, bundle entries): the automation service rejects agent_profile_id on /v1/preset/prompt and /v1/preset/plugin, so the Prompt and Plugin actions hide it and switching to them clears a chosen profile. Leave it on Default except in the Upload tarball bullet. A cleared choice is stored as an empty id and shown as Default (#18111).
  • The automation detail page has no main landmark: read its title with browser text 'role=heading[level=1]', not 'role=main >> …'.
  • The import success toast is the only path to View automation and disappears after about 5 s.
  • Continue with local setup silently creates the OPENHANDS_URL secret (value: the app origin) the first time; F14's list then has an extra row.
  • The Install button in a template's install dialog verifies the MCP connection before saving, so dummy credentials cannot complete the queue; nothing is saved on failure.
  • Creating the first automation ticks Schedule a task in the sidebar Getting-started checklist (F02); a run that checks the checklist should do so before this file.
  • Known issue #17959: a template search with no matches leaves a blank area with no message (F22.templates-search).
  • Known issue #17436 (F22.setup-review-confirm): Prompt-preset automations created after the Canvas 1.17.0 upgrade fail on the first conversation events request while older tarballs work, so the UI shows Failed runs. Recheck with a real LLM key and the reported automation 1.11.1/Agent Server 1.46.0 deployment plus pre-upgrade and finish-tool-wired tarballs; retain this version-specific report separately from the plugin-preset Cloud failure in #16611.
  • Known issue #15509 (F22.setup-tarball-create): A native Windows event-triggered automation fails while uploading its workspace tarball with Path must be absolute. Recheck a Windows local stack and real event delivery, then observe its failed run/upload; a POSIX tarball upload is not a Windows path-validation check. This run has neither Windows nor the webhook fixture.
  • Known issue #16611 (F22.setup-plugin-create): a minimal plugin-preset Cloud automation fails on its in-sandbox conversation/events request with HTTP 500, while the identical prompt preset works. Live prerequisites: an authenticated Cloud account, a real model key, and a reviewed no-op plugin fixture for the paired cases. The reported cost-zero error is visible in run logs, but a local prompt-only check cannot establish the Cloud plugin-loading behavior.
  • Known issue OpenHands/software-agent-sdk#5729 (F22.setup-repository-list): prompt and plugin preset workers call RemoteWorkspace.clone_repos, which asks for lowercase provider-token secret names. A private repository can therefore fail to clone when only the documented uppercase GITHUB_TOKEN (or GitLab/Bitbucket equivalent) exists. Recheck an owned preset with a private repository and only its uppercase secret, using an authorized disposable repository or a reviewed token-gated local Git fixture; a public repository and the Canvas Agent Server's separate Git-provider service do not exercise that lookup.

F23 Automation detail, runs and editing

41 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • The automations QA checklist (F28) expects edit-mode Test to run the current form state; the panel calls dispatchAutomation(<id>), which runs the saved automation. F23.edit-test records current behavior; whether it should change is a product decision, not a map correction.
  • The Logs dialog's panel keeps long content reachable (F23.run-logs-modal, #18175 / #18173). Re-verified 2026-10-13 (revision 2bdc0e9d, SDK 1.54.0, automation 1.19.3) with a 30-line outcome_summary at 1440×1000, 390×844 and 390×500: the panel is testid=run-logs-body (max-h-[80vh], overflow-y: auto) with a sticky header (Logs + X stay visible); the Output box no longer scrolls on its own, the whole panel does, and its end (=== RESULT === ALL_OK) is reachable at the bottom. Verified 2026-10-10 on an independent fresh Linux Local stack at product revision 5a8c3832154b14d2dad651801c0ab3f74c96f02e, SDK 1.54.0 and automation 1.19.3: a genuine DeepSeek finish produced a 4,596-character outcome_summary and 1,079-character metadata message; summary/metadata endings, Output/Error tabs and the true stdout end were reachable at 1440×1000, 390×844, 1440×500 and 390×500. The header/X remained visible, closing did not navigate, and phone had no horizontal overflow. Preserve those long-content and short-height checks when retesting. The verified scope is pinned Linux Local; macOS, Cloud and legacy/custom finish schemas need their own checks. browser bbox 'role=dialog' measures the full-screen wrapper, not the panel: measure the panel with browser eval.
  • The detail page has no main landmark: browser snapshot 'role=main' times out. Snapshot the page, or scope by section headings (role=heading[name="Configuration"] >> xpath=ancestor::div[2]).
  • The kebab menu has no test ids; role=list >> has-text=Tarball scopes the detail kebab (the dashboard kebab has no Tarball, so F21 uses has-text=Export).
  • Kebab Edit (detail and dashboard) opens /automations/setup?automationId=<id>. The edit page's Save and Test report API failures with the API's message (see F23.edit-validation, F23.edit-save-error), and its Automation updated toast covers Test/Save/Close until the pointer moves away.
  • Known defect: clearing At on a preset schedule and saving stores midnight with no message (#17948 item; with the old modal it was silently ignored).
  • On a local backend the Logs dialog reads every run's output from the server-level /api/bash/bash_events/search, with or without a conversation. It caches what it fetched: reload before checking its requests with browser network.
  • Known defect: prompt runs also have a bash_command_id, so every row shows the logs icon, and a prompt run that ended without a conversation is labelled as a script run (F23.run-no-conversation) (#17948).
  • The automation service (1.19.0 here) keeps repos and plugins only under preset_metadata; the UI reads them from there. Repositories show owner/repo without the host (a GitLab URL reads like a GitHub one), a repo without a ref gets no badge, and Export keeps only the first repository (all plugins). The Notification field reads a top-level notification that the service has no field for, so it never renders on a local stack (#16644 tracks removing or mapping it).
  • The Schedule field shows the raw cron (0 9 * * *) and no timezone: the service returns no schedule_human and no top-level timezone, so the Edit dialog also omits its Timezone: hint.
  • run-phase never appeared in the log here (phase_code stays null on the local service), so the phase chip is not reachable on this stack.
  • An LLM profile that does not exist (model: "qa-no-such-profile") does not fail the run: it ran and succeeded with the active profile. Use timeout 1 to get a failed run; it takes several minutes to fail.
  • Load more in the log (F23.activity-log-more) needs more than 20 runs of one automation; at about 35 s and one model call each it is left out of a normal pass (not-run).
  • The script section needs a tarball automation. The account-free way to get one is F22's Daily news digest bundle entry (it uploads its bundle and creates the automation); the custom-automation Upload tarball action takes control-openhands fixture tarball. Its kebab Edit shows generated starter Python instead of the saved bundle (#18341, see F21.edit); a cold load of /automations/setup?automationId=<id> shows the Existing code bundle summary.
  • Starting… on Run now lasts only for the dispatch request (under 50 ms locally); --observe does not catch it.
  • The run row is a plain <a href>, so opening a run's conversation reloads the whole app (a document request in browser network), unlike sidebar navigation.
  • A 404 detail request is retried three times before Automation not found shows (about 7 s of skeleton) (#17948); wait with wait-text, not count.
  • browser fill on the Timeout spinbutton with letters fails in Playwright and, after its 30 s retry, the dialog was found closed; use numeric values (1.5, 0, 1801) to reach the timeout errors.
  • The Tarball download is named <name>.tar (spaces become _) but is gzip-compressed (#17948); browser downloads --inspect does not list tar entries, so inspect it with tar -tzf.
  • Debug with OpenHands starts a real agent with the openhands-automation skill and the automation API key. It acts on live automations: here it looked up QA_F23 Fail by name (a newer fixture with the same name), PATCHed its timeout back to 600 and dispatched it, which adds runs and conversations. Stop it once it has started: on its page control-openhands browser click 'testid=stop-button', then control-openhands conversation wait <id> --until paused,finished,idle --timeout 45 (paused).
  • A successful run leaves a conversation <name> — <UTC timestamp> that stays after the automation is deleted (conversations family owns deleting it).
  • Known issue #15465 (F23.configuration-event F23.activity-log): An automation configured for Agent Canvas release events never runs automatically, although Run now works. Recheck a real signed GitHub/Forgejo release webhook through an externally reachable responder into a disposable automation and inspect its activity log; local manual dispatch cannot verify event delivery. No external repository webhook credentials or controlled release-event fixture are available.
  • Known issue OpenHands/software-agent-sdk#5279 (F23.run-linked-conversation): An automation that attaches to an existing remote conversation can complete with no conversation link in its run, because the workspace callback does not register the attached id. Recheck a Cloud automation script that calls RemoteConversation.attach on an owned stable conversation and exits through the real workspace completion callback, then inspect the Activity Log link. Blocked without that authenticated Cloud workspace/callback context; a plain local GET is not callback proof.
  • Known issue OpenHands/software-agent-sdk#4705 (F23.run-status-polling, F23.run-logs-modal): GitHub MCP tool events can omit inputSchema, causing an automation's RemoteConversation decoder/run log to report failure while the agent continues, so task and automation statuses disagree. Recheck a real GitHub-MCP-enabled run using the affected client/server versions and its retained tool-definition event; the issue does not give those versions. A generic MCP fixture does not establish the reported GitHub/version branch, and genuine run comparison needs a model key.
  • Known issue OpenHands/software-agent-sdk#4260 (F23.run-linked-conversation, F23.run-logs-modal): Cloud PR-review automation can remain at review in progress and time out without posting a review; its event webhook also fails to serialize SecretStr, breaking the UI stream even on successful reviews. Keep the stream-serialization failure distinct from the empty-response/600-second run-budget failure. Recheck with an owned Cloud reviewer/webhook deployment, authorized disposable review target and real model credentials; a local script run proves neither production path.
  • Known issue OpenHands/automation#286 (F23.run-status-polling): Multi-worker/replica automation deployments can run duplicate watchdogs that query the same sandbox and hit rate limits, falsely failing runs. Recheck the real deployment's worker/replica configuration with an owned long-running Cloud run and sandbox verification traffic; this single-worker local stack cannot prove distributed scheduler/watchdog ownership.
  • Known issue OpenHands/automation#219 (F23.run-status-polling): Starting a Cloud automation at the user's sandbox cap can pause an existing interactive session instead of reaching the graceful Skipped–Limit Reached path. Recheck only on an owned disposable Cloud account at its recorded cap, with a live interactive fixture and a new automation run, then observe both run and conversation states. This run has no Cloud account/cap fixture; do not evict a production session as a test.
  • Known issue OpenHands/automation#113 (F23.run-linked-conversation): Scheduled plugin jobs can start conversations visible in the archived list while the automation run UI says no conversation started. Recheck the affected automation/run ids, archived conversation ids and deployment version, using the real callback relationship; these identifiers and version are not provided. This is a reported missing-link state, not proof that no agent ran.
  • Known failure OpenHands/automation#514 spans Home cancellation (F03.cancel-in-flight-run) and F23.activity-log/F23.run-logs-modal: on 2026-10-12 a genuine Home Cancel run changed the owned bounded local worker's row to Cancelled, while its Python entrypoint remained alive in the owned launcher ancestry. The F03 driving bullet owns upload/create/cancel steps and retained evidence. The worker later reached its 180 s bound and exited; a cancelled status alone does not prove process cleanup.
  • Known issue OpenHands/software-agent-sdk#4388 (F23.run-status-polling, F23.run-logs-modal): incremental SDK Bash output polling can return empty pages before newer chunks when order__gt is applied after the file-page boundary. Canvas's frontend log reader does not send that filter, but automation launch preparation and prompt/plugin preset repository clones use (Async)RemoteWorkspace.execute_command, whose output poller does. Recheck a bounded genuine preset/setup or repository-clone command that emits enough chunks to cross the 100-event boundary, correlating its final BashOutput and real run result; a frontend Logs read or a one-output command does not cover the incremental branch.
  • Known issue OpenHands/software-agent-sdk#5937 (F23.run-status-polling, F23.run-logs-modal): prompt/plugin preset workers attach AUTOMATION_CONVERSATION_ID for continued conversations and then call RemoteConversation.run(). If that same conversation is already running a tool for more than 30 s, the join request can time out and fail the automation worker while the agent continues. Recheck an owned continued-conversation automation with a genuine already-running long tool and correlate worker error, agent status and final run result; a fresh conversation or one normal first run() does not exercise the join. No model execution is authorized in this pass.
  • Known issue OpenHands/software-agent-sdk#5938 (F23.run-status-polling): prompt/plugin preset workers register an event callback that forwards ActionEvent.summary as the live phase. After an events-socket reconnect, REST-reconciled events reach SDK state but are not replayed to that callback, so summaries emitted in the gap can be missed. Recheck an owned real preset worker through a bounded reconnect window while a genuine action is recorded; compare callback-driven phase with stored events. Current Canvas phase rendering has its separate #17121 gap, and the worker's completion metadata is not claimed lost merely because this callback missed an event.

F24 Automation Git Sync

26 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • Configuring a repository is what turns sync on: the backend's pause switch defaults to on, so the first save of a URL flips the pill to Enabled even though the switch was never touched, and clearing the URL turns it off again. The help text under the switch says so (Sync is on as soon as a repository is configured. ...). A pause is the exception: it survives clearing and re-entering the URL, so the pill stays Disabled until the switch is turned back on.
  • A local path (or file:// URL) is a valid remote, which is what makes this family drivable without credentials. Use a branch other than the fixture's checked-out main: git's default receive.denyCurrentBranch refuses pushes to a non-bare repo's checked-out branch (not driven here; every recipe uses qa-sync).
  • The reachability check runs only when repository URL, branch or token changed (git ls-remote, 20 s timeout). Interval, path, author, encryption key and the enable switch save without it. A check that cannot run at all (old backend, network error) never blocks a save.
  • The second Save after a failed check saves the same values without checking again; editing any checked field re-arms the check.
  • The activity row (git-sync-activity-row, data-state running/succeeded/failed) is page state: it disappears on reload and does not appear for a background cycle that starts and ends between two idle polls (15 s). Assert background cycles through the commit hash, not the row.
  • Feedback colours are semantic tokens (#18346): the check-failure block, the Last sync error banner and a Sync failed row use the danger surface and feedback-error text (rgb(231, 106, 94) on OpenHands-Neutral, rgb(192, 40, 45) on white under Light+), and Sync complete uses success (rgb(165, 231, 94) Neutral, rgb(15, 115, 77) Light+); verified 2026-10-13 on desktop. Read them with browser eval of getComputedStyle(...).color on the text elements inside git-sync-check-failure, git-sync-error-banner and git-sync-activity-row after switching themes on /settings/app (F16.color-theme).
  • Toasts (Git Sync settings saved., Enable Git Sync before triggering a sync.) last a few seconds: read them with browser wait-text immediately after the click, never after a long --observe window.
  • Save's label cycles Save Changes → Checking repository... (only when a checked field changed) → Saving... → Save Changes in about 100 ms against a local repo; --observe 'testid=git-sync-save-button' records it.
  • Setting, rotating or clearing the encryption key re-exports the automation even when its definition is unchanged. Each key-only save sets dirty_count to 1; the next sync creates a commit with ciphertext after set, different ciphertext after rotation and plaintext after clear. Read the dirty count and exported file before any automation PATCH; a separate dirty export tests a different contract.
  • Nothing on the page says whether a token is stored: the token placeholder is always Leave blank to keep the current token, and the author fields always reload blank. Prove author changes from the commit (git log --format='%an <%ae>').
  • The card's Repository value drops embedded credentials, as link text and as plain text: an http(s) URL loses its whole user:token@ (a token alone in the user slot too), other schemes keep the user and lose only the password (ssh://git@host:2222/...), and the rest stays as configured. The Repository URL input and GET /api/automation/v1/git-sync/status still carry the credentials, so a screenshot of the form shows them. Never type a real token into the URL.
  • The page has no Automate sub-navigation (Dashboard / Templates) and no command-menu entry; leave through Back to Automations (role=link, not a button).
  • Deleted automations stay in Pending changes until a cycle pushes the removal; after cleanup with sync off the card can read Pending changes 2. Sync a deletion pushes it while the repository is still configured.
  • An untouched Enable Git Sync switch follows the server: after another admin pauses sync, the next idle poll flips both the pill and the switch off, so turning it back on is one label click plus Save.
  • F24.unsupported uses --automation-ref for the old Git tag; without access to that source it stays blocked. The current CLI also supports --automation-version V for a PyPI release (verified with 1.19.3 on 2026-10-10). Backend overrides come from these explicit launch flags; shell OH_AUTOMATION_VERSION is not forwarded.
  • Arrange with api ... --write only to mark automations changed or to simulate another admin; the save, check and sync steps must go through the form.
  • Known issue OpenHands/automation#586 (F24.sync-now): a YAML-only repository edit can replace an unchanged uploaded script bundle because canonical gzip bytes retain their creation second. Recheck a disposable Git Sync repository and script automation, change only YAML, arrange the canonical rebuilds across different seconds, and compare the real saved upload reference before and after sync. Equal decompressed contents or a serializer-only probe do not prove that Canvas reuses its upload.

F25 Backends, Cloud and sharing

33 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • 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.

F26 Launcher modes, Docker, desktop and library

34 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • control-openhands launch covers only the full stack and --public. Partial stacks and --host need a hand-started launcher with its own HOME, OH_CANVAS_SAFE_STATE_DIR and all three OH_CANVAS_SAFE_*_PORT variables; without them it writes into the real ~/.openhands/agent-canvas and grabs the default ports 8000/18000/18001/3001 that another agent may be using.
  • A hand-started launcher's first start downloads the agent-server into its private HOME cache (30–90 s). Prefix assignments are expanded left to right, so HOME=$Q/home UV_CACHE_DIR=$HOME/.cache/uv points the cache into $Q/home too, not at your real cache. Reuse one $Q for repeat launches to keep it warm.
  • The browser daemon refuses other origins unless you pass --allow-external to browser goto. Each port is its own origin, so a partial-stack page starts at first run again (onboarding and a consent dialog), independent of your run's onboarding.
  • In a frontend-only UI the Add a backend step's Next is enabled with an empty key, while on a LAN or --public stack it is disabled until a key is typed: the host the step probes differs (503 vs. auth-required).
  • A partial-stack origin keeps its stored backend in the browser profile across launches, so a second pass lands straight in the shell. control-openhands browser reset brings back the first run on every origin; then control-openhands browser goto / and control-openhands onboard --skip restore your own run's baseline.
  • Which screen a frontend-only origin shows depends on the browser, not the launcher: a profile new to the origin gets Add a backend (first run owns the initial backend), while a profile that stores a backend for that origin (the default-local entry a full launcher on the same port seeded, or any backend added there earlier) gets the recovery gate of F25.recovery-gate as soon as that backend answers 503 (F26.frontend-only-returning). The no-toast check belongs to the first-run state only: on the gate, browser toasts --history lists generic An error occurred status toasts raised by the failing 503 probes (two within 12 s here), with no hint of which request failed. Known failure (reproduced 2026-10-08): both toasts come from the free-models hydrator's GET /api/llm/models/verified 503, which no meta.disableToast guards on that screen (nor does backend-version.tsx); #18160.
  • Stopping a backend-only instance while the frontend-only page that uses it stays open is the same backend-down state as F25.recovery-gate after a reload; keep the returning-user bullet above as the frontend-only-specific check (it reproduces the default-local case).
  • The launcher serves the bundled VS Code editor. #17660 made that opt-in (OH_CANVAS_ENABLE_VSCODE=true) and #18048 reverted it, so only a checkout between the two needs the variable (launch --vscode sets it). A launcher that serves it reserves /vscode/: a frontend-only stack answers 503 there and logs [static] /vscode -> 503 (rejected); a stack with an agent-server lists /vscode → http://127.0.0.1:<agent-server port + 1000> in its ingress banner and hands the agent-server that editor port (19961 for the hand-started launchers here, 19000 for the desktop app), which the launcher's own port check does not cover. On a checkout between the two without the variable, /vscode/ is an ordinary SPA URL (200). Here the agent-server has no editor binary (VSCode server binary not found, VSCode will be disabled in the launcher log, /api/vscode/status {"running":false,"enabled":true}), so the editor port stays free even then.
  • The port-in-use error is printed twice, the second time with a Node stack trace, and suggests PORT=<other> rather than --port (#17949). The --public key error's example says npm run dev -- --public instead of the npx command.
  • A backend-only instance's /server_info.runtime_services still describes the ingress as routing /* to the frontend, which it does not run (#17949).
  • In this sandbox node_modules/electron/dist is absent and node node_modules/electron/install.js fails with ERR_ASSERTION (2026-10-13), so the F26.desktop-* rows stay blocked here.
  • The Electron app hardcodes ports 8000/18000/18001 (and the static server takes 3001), ignores PORT, and refuses to start as root without --no-sandbox (Running as root without --no-sandbox is not supported). npm run desktop first runs build:app, which rebuilds build/ under every other run: start node_modules/.bin/electron --no-sandbox electron directly against the existing build.
  • Never stop the desktop app with one group-wide signal: when Xvfb dies with Electron, the app exits without stopping its backend, which is left orphaned (own process groups, parent 1) on 8000/18000/18001/3001 and blocks the next start. If that happens, find the orphans by their ports' command lines (ps -eo pid,pgid,cmd, look for --port 18000, --port 18001, --port 3001, ingress.mjs --port 8000) and stop each group by id.
  • The generated session key lives in $HOME/.openhands/agent-canvas/api-key.txt, not under OH_CANVAS_SAFE_STATE_DIR: a hand-started launcher without a private HOME reads and writes the operator's real key file.
  • A browser origin keeps the backends it stored on an earlier pass: after the LAN pass on 18960, a later loopback launch on 18960 shows the stale QA_LAN backend (red, old key) instead of onboarding, although the page injects a valid key. Assert key-injection bullets through the window globals, or browser reset first.
  • Electron's splash shows each raw service-log line (agent-server: {"asctime": ...) as its status text during boot; the named phases only appear between them.
  • build:lib from a copy whose node_modules is a symlink emits bundled dependencies under dist/<absolute node_modules path>/...; that path is an artifact of the copy, not of the product.
  • **Bug candidate (F26.host-bind-lan-optin):** scripts/bind-host.mjs tells npm users to pass --allow-lan-session-key, but neither bin/agent-canvas.mjs nor scripts/dev-with-automation.mjs parses or forwards it (only scripts/static-server.mjs and the Docker entrypoint do), and unknown flags are silently ignored (#17949, whose first item is exactly this; reproduced again on 2026-10-08 at d2c89252d).
  • Known issue #17946: the npm launcher never creates its TMUX_TMPDIR, so hand-started instances share one tmux server and a restart of one resets the others' terminals.
  • Known issue #17763 (F26.docker-image): The Docker entrypoint can leave protected Agent Server APIs unauthenticated when only LOCAL_BACKEND_API_KEY is supplied, because its export to OH_SESSION_API_KEYS_0 is inside the generate-key branch. Recheck an owned container with a dummy custom key: unauthenticated GET /api/conversations/search must be 401 and the same request with that dummy key 200. /server_info is intentionally public and is not an auth probe. Blocked here without a Docker daemon.
  • Known issue #17623 (F26.helm-chart): The chart defaults service-account-token automounting to true even with RBAC disabled, exposing an unused cluster credential to agent processes. Recheck a disposable Kubernetes installation using the default values and an explicit opt-in; inspect token existence/readability only, never its contents. Blocked without Helm and a Kubernetes cluster.
  • Known issue #17470 (F26.docker-image): On Apple Silicon under Colima/Virtualization.framework, the image's Python services can exit with SIGILL while the frontend leaves onboarding at Disconnected. The report isolates import litellm and compares OPENSSL_armcap=0; recheck on the affected ARM/Colima guest with the reported cryptography/OpenSSL combination. A native macOS npm stack is not that container environment.
  • Known issue #17112 (F26.desktop-main-window F26.desktop-external-links): Desktop main-window navigation, an OAuth popup redirect, or a nested popup can render a remote page inside the app frame. Recheck all three on an owned running Electron shell with a harmless remote page and loopback redirect fixture; the browser harness cannot operate Electron windows and the installed Electron binary is unavailable here.
  • Known issue #16872 (F26.desktop-external-links): Non-browser URI schemes were reported to reach the OS handler from the desktop popup flow. Current source now parses hosts and restricts external protocols; recheck refusal in a running owned Electron shell before concluding resolution. Observe an inert denied test scheme without invoking any real OS handler. The browser harness cannot drive Electron and no installed Electron binary is available here.
  • Known issue #16774 (F26.desktop-boot-splash F26.desktop-main-window): The macOS 1.14.0 DMG installs an app that Gatekeeper reports as damaged instead of booting. Recheck the actual downloaded signed/notarized release artifact through install and first launch on macOS; starting source Electron or removing quarantine would bypass the reported failure. That release DMG/signature fixture is unavailable in this run.
  • Known issue #16720 (F26.runtime-services): CPU-heavy commands in several conversations can starve Agent Server and stop the UI loading; the report requests lower priority for agent commands than the service. Recheck with a disposable bounded workload, recorded CPU/resource limits and real command observations, never arbitrary stress on the shared machine. The exact Rust workload and affected resource envelope are missing, and real agent execution needs a model key. The backend counterpart is OpenHands/software-agent-sdk#4575.
  • Known issue #16690 (F26.docker-image F26.runtime-services): A Docker deployment with nine chats becomes slow/disconnected, then reloads lose the list and startup proxies report ECONNREFUSED. Recheck on the affected Windows-host Docker configuration with retained conversation state and a controlled workload; creating a few empty local chats cannot prove the reported resource/runtime failure. This run lacks Docker and the original conversation/workload fixture.
  • Known issue #16511 (F26.docker-image): The documented Docker bind mount can be unwritable to container uid 10001, killing Agent Server/automation while the frontend still serves Bad Gateway. Recheck a disposable host-owned mode-700 mount and a writable control against an owned image; never chmod the operator's real state directory. Blocked here without Docker.
  • Known issue #16300 (F26.runtime-services): The npm launcher can fail while building LiteLLM native dependencies when resolved AWS crates require a newer Rust compiler, leaving backend services unavailable. Recheck an isolated uncached uv environment with litellm==1.95.0, the reported Rust 1.93.1/toolchain/platform and dependency resolution; a warm current backend cache cannot exercise that install failure. The historical dependency/toolchain fixture is unavailable.
  • Known issue #16208 (F26.runtime-services): On Fedora Rawhide/Python 3.14.6, settings work but starting any conversation returns HTTP 500. Recheck a native Fedora environment with the reported SDK/runtime and model/profile, including the underlying server traceback; this macOS current-runtime stack cannot prove that platform-specific failure.
  • Known issue #15434 (F26.remote-backend): The embedded VS Code action on a self-hosted reverse-proxy origin was reported to reopen Canvas because its advertised URL omitted the separate editor port/path. Current launchers include an ingress /vscode/ route; recheck against the affected HTTPS proxy deployment with an actually available editor binary and emitted URL before concluding resolution. This run lacks that self-hosted proxy/editor deployment.
  • Known issue OpenHands/software-agent-sdk#5373 (F26.remote-backend): A self-hosted Agent Server using default Rich traceback logging can wedge its event loop and grow memory when a websocket error has a cyclic/deep exception chain; Canvas then times out or disconnects. The npm launcher forces JSON logging, so rechecking needs a separately hosted default-Rich backend plus the reported pathological peer/backlog reproducer and bounded memory/time isolation. That original peer/driver is not included in the issue body; do not synthesize a server patch or stress the shared host and claim a live UI reproduction.
  • Known issue OpenHands/software-agent-sdk#5310 (F26.docker-conversation-runtime): A Docker conversation can lose the auxiliary title profile at the runtime boundary, leaving an ACP conversation untitled or producing an unrelated title-auth error while the agent still works. Recheck a saved reachable title profile and an ACP conversation inside an owned per-conversation Docker runtime, comparing its title/profile metadata with the host; inspect only profile names and credential-presence booleans. Blocked without Docker and real auxiliary-title/ACP credentials.
  • Known issue OpenHands/software-agent-sdk#5307 (F26.docker-conversation-runtime): New worktree on a Docker-backed conversation can create git metadata whose reverse path exists only inside the container, so the still-live host worktree reads prunable gitdir file points to non-existent location. Recheck a disposable real git repository mounted into an owned conversation container, comparing host and container git worktree list/status while it is alive; a local worktree does not test the filesystem boundary. Blocked without Docker.
  • Known issue OpenHands/software-agent-sdk#4901 (F26.runtime-services): closing an ACP conversation on POSIX is reported to leave the adapter's descendant Node/CLI processes alive after the top npm process exits. Recheck an owned authenticated ACP provider command chain, record its exact descendant identities before close and inspect only those identities afterwards; cleanup must target those owned groups. The current run has no authorized authenticated ACP runtime for this path.
  • Known issue OpenHands/software-agent-sdk#5974 (F26.runtime-services; workspace picker in F03): a saved-workspace mutation waiting for its registry file lock can block the Agent Server event loop, including /alive, until the lock is released. Recheck only an owned registry using a bounded lock holder, an independently confirmed waiting mutation, concurrent liveness probes and a successful post-release readback; the original evidence uses Linux kernel lock-wait readback. A healthy idle server or concurrent writes without deliberate lock contention do not cover it.
  • Known issue OpenHands/software-agent-sdk#5731 (F26.cli-public-needs-key): whitespace-only session keys remain configured credentials in the Agent Server. The Canvas launcher forwards a nonempty LOCAL_BACKEND_API_KEY verbatim, so a whitespace-only deployment value can leave the public login unable to use its intended key. Recheck an owned loopback-only public deployment with a whitespace-only supplied key, its actual HTTP/WebSocket authentication and a normal nonblank control; never test the server's default broad bind on an exposed interface. The SDK's missing-OH_SECRET_KEY cipher fallback is a separate configuration branch from a Canvas launcher that supplies an encryption key.

F27 Workspace tools: terminal, browser, planner, tasks and usage

30 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • After a reload the Task List (and tab bar entry) appears only once history has loaded; wait about 2 s before counting [data-active="true"] rows.
  • [data-active] is also used by sidebar conversation cards: always combine it with has-text=<task title>.
  • browser click --observe takes a plain CSS selector ('[data-testid=compact-context-button]'); the testid= form fails with a querySelectorAll SyntaxError.
  • "Create a plan" leaves that conversation in plan mode: the next composer message goes to the planner helper, which has no launch_child_conversation or canvas_ui_control. Use a separate conversation for client-tool prompts.
  • The child-launch toast's Open link animates; browser click on it times out with "element is not stable". Read the child id from the parent's [child-conversation] message instead.
  • GET …/file/download?path=…/.agents_tmp/PLAN.md 404 and GET /api/llm/balance 404 are expected app-origin errors (no plan yet; no balance endpoint). Count them in browser errors --app-only but do not report them.
  • Escape closes the composer context popover and the Overview "..." menu and returns focus to the trigger (#18044); an outside click, or a second click on the trigger, closes them too.
  • The Overview column is 28 px narrower than its content (scrollWidth 296, clientWidth 268). After some clicks inside it, Playwright's scroll-into-view shifts it and the card looks cut off on the left; browser reload restores it before taking screenshots.
  • The agent may summarize a client-tool action with its own title (for example "Launch local child conversation to reply PONG") instead of "Launching a child conversation"; assert the toast and events, not the chat title.
  • The Overview peek appears only while the drawer is open (canPeekOnHover needs isRightPanelShown); a browser goto or reload starts with the drawer closed.
  • The Overview's repo and branch rows depend on a bash probe (useLocalGitInfo) that runs only while the conversation's status is idle, running, waiting for confirmation or finished and the agent is not in error. Right after conversation start the probe has run and its result stays cached on the page, but on an errored conversation a reload hides the rows again, and conversation pause does not apply to an errored conversation. Read the rows before any goto or reload.
  • At phone width (and in a narrow drawer) the tab row shows only the tabs that fit; the rest are reachable only through the tabs menu (conversation-tabs-menu-open-<tab>), and which tabs fit depends on the active tab's label width.
  • Plain agent-canvas instances on one machine share the tmux socket /tmp/tmux-<uid>/openhands (OpenHands/OpenHands#17946), so another instance stopping can reset this one's terminal. control-openhands launch gives each run its own TMUX_TMPDIR, so in verification runs a reset means this run's agent server restarted.
  • Agent Tools & Metadata remembers the last tab (it reopens on Available Tools).
  • The Overview menu opens only Pull Requests (conversation-overview-menu-open-git-pull_requests); its testids are conversation-overview-menu-open-* and -pin-* for workspace and the Git rows only.
  • Known open issues: terminal history after reload (#17566) still fails (reproduced 2026-10-06); info modals at phone width (#17562) pass.
  • Agent tab requests (open_tab, navigate_to_file, chat file links) are saved per conversation like a user tab click (#18045): after the agent opens Terminal or a file and the page reloads, the drawer starts closed and the panel toggle reopens it on that tab; clicking that tab then closes the drawer. At phone width they navigate to /conversations/<id>/panel.
  • Known issue #17774 (F27.planner-empty F27.planner-plan): Planner reads on a native Windows local backend reject drive-letter/mixed-separator workspace paths with Conversation file path must stay inside the workspace before a download request. Recheck both a scratch workspace and an opened drive-letter workspace, with and without .agents_tmp/PLAN.md; this run lacks a Windows local Agent Server.
  • Known issue #17198 (F27.terminal-output): Native Windows desktop agents intermittently return [Command finished execution with no output] for real file-listing/terminal commands and retry them. Recheck with the affected Windows installer/runtime, project agent definitions and an exact command/workspace; no deterministic pattern or backend trace is provided, and this run has no Windows desktop runtime. The backend counterpart is OpenHands/software-agent-sdk#4963.
  • Known issue #16551 (F27.browser-screenshot): On native Windows, the browser tool chooses system Chrome over installed Playwright Chromium and times out before returning state/screenshots. Recheck default browser selection and an explicit executable-path control on a Windows host with both browsers and the reported browser_use runtime; the verification Chromium itself is a different tool. Blocked here without Windows and real browser-tool execution credentials.
  • Known issue #16581 (F27.terminal-output): A module-scoped terminal command index can suppress output in a second simultaneously mounted Terminal and reset the first when either unmounts. The report describes a future split-panel/embedded concurrent mount rather than a supported standalone entry point. Recheck in a host that actually mounts two independent Canvas Terminal consumers with real command histories; the current harness has one Terminal route and no such host fixture.
  • Known issue #17384 (F27.launch-child): A file-based delegated subagent using a saved profile/provider connection can fail with HTTP 401 while the top-level conversation succeeds, because encrypted provider credentials are not decrypted in that backend factory path. Recheck a reviewed .agents/agents/ worker, encrypted provider connection and saved profile using a real key, comparing parent and worker authentication. This is a delegated SDK subagent path, distinct from the client-tool child-launch recipe; neither a dummy key nor an unencrypted profile proves it.
  • Known issue OpenHands/software-agent-sdk#5533 (F27.terminal-output): A slow interactive Bash startup can make the first terminal command return early with empty output and a false successful exit code. Recheck both standalone and pooled tmux using a private .bashrc with sleep 0.4, then a bounded sleep 2 first command and its real observation. The shell fixture can be arranged, but the mapped agent-command/Terminal path cannot run here without a model key.
  • Known issue OpenHands/software-agent-sdk#5322 (F27.terminal-output): The default tmux pane pool or Windows terminal can let git log -p start an interactive pager, so the next agent command receives stale paged output instead of its own result. Recheck a reviewed large git fixture, a real installed pager and a follow-up marker command on each affected terminal backend. The fixture is arrangeable, but the mapped agent-tool path needs a model key (and the Windows half needs Windows).
  • Known issue #17383 (F27.browser-screenshot; dependency counterpart OpenHands/software-agent-sdk#5152): unlocked dependency resolution can silently remove the agent's browser tools. The actual 2026-10-12 pinned runtime resolves browser-use 0.11.13, mcp 1.30.0 and fastmcp 3.4.8; its genuine BrowserToolExecutor constructs successfully. That narrowly fails to reproduce the SDK report's specified mcp 2.x import failure. Fresh constructor recheck on 2026-10-16 at b34ac59, using the actual Agent Server 1.55.0 interpreter and private HOME, again resolves browser-use 0.11.13, mcp 1.30.0 and fastmcp 3.4.8 and constructs successfully; no browser operation or model was run. The Canvas/global browser-operation path still needs an authorized real LLM and a genuine browser-enabled agent issuing a browser_* operation; construction alone does not establish that feature works.
  • Browser availability also depends on OpenHands/software-agent-sdk#5899 (F27.browser-screenshot; canonical tool-selection note in F13): a Chromium installed only under PLAYWRIGHT_BROWSERS_PATH is missed by the agent-tool probe. Recheck the real browser-tool operation on a host with only that custom cache and no standard browser, default cache or PATH executable. This Mac's user Chrome must remain installed; an executor constructor or the verification browser is not a successful agent browser operation.
  • Known issue OpenHands/software-agent-sdk#5835 (F27.overview-identity): the local Overview Git probe uses conversation-scoped Bash execution; an idle-eviction pass can kill scoped work without a final output. Recheck a disposable short-idle-TTL server while a genuine Canvas Git probe is still executing, with independently observed command lifetime and the eventual output/result. The report's standalone asynchronous sleep reproduction is supporting backend evidence, not proof of a Terminal-tab command, which uses the agent event stream instead.

F28 Automation setup page

13 behaviors · Read this family’s scripts

Family notes & prerequisites for known issues
  • A toast can cover the toolbar (Save draft, Test, Close) and stays while the pointer rests on it, so the next toolbar click times out ("subtree intercepts pointer events"). After each save, browser hover a form field (for example testid=automation-setup-name) before clicking the toolbar again, as F22's Gotchas say.
  • Drive inputs with browser fill: setting .value from browser eval does not reach React state for every field, and a save then sends the old value.
  • Change one field per Save draft when checking a validation message: a save shows only the first server error, so two edits in one save hide the second.
  • A cold load of /automations/setup?draftId=<id> or ?automationId=<id> (a reload or a typed URL) opens the form: re-checked 2026-10-14 on a control-openhands launch of main with this family (both read their Name back). An earlier instance of unknown build stayed on Loading saved draft… for more than 20 s; if that returns, compare with opening the same draft from the dashboard.
  • The draft bar duplicates the prompt error: Field required appears both under Prompt and in the bar.
  • Kebab Edit on a Custom Python automation shows the code editor instead of the saved bundle summary: open bug #18341 (F23), with fixes in PRs #18353 and #18444.
  • Escape on the setup page does nothing, by design (F23.edit-close).

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.