EN / field notes OpenHands feature map

OpenHands / F03

Home and starting work

Home is the "What do you want to work on?" screen at / and /conversations. A user describes a task in the composer and, on a local backend, can first pick a workspace folder (Open Workspace, folder browser, Manage Workspaces), a workspace mode and plugins; submitting creates a conversation and opens it. A warning banner replaces a silent failure when no LLM is configured. Below the composer, home offers recommended automations, a pinned-automations grid and an activity list of enabled automations with per-item actions.

39 mapped behaviors · 34 recipes and supporting checks · source snapshot 9 October 2026
From upstream main at 8793c111. Read the maintained source.

How to get to it

  • Sidebar New Chat (sidebar-conversations-link), which opens /conversations; the logo does the same (see F02).
  • Direct URL / (unless a page is pinned as home, see F02) or /conversations.
  • Command menu (Control+k, Meta+k or command-menu-trigger): search "New chat", choose New chat.
  • Under the composer: Open Workspace (open-workspace-button) or, once confirmed, the preview bar (home-git-control-bar-preview); Plugins (open-plugin-picker).
  • In the Open Workspace dialog: the combobox (workspace-dropdown) and its footer + Add Workspace (add-workspaces-button) and Manage Workspaces (manage-workspaces-button, shown once any workspace or parent exists); in Add workspaces, the Favorites/Locations sidebar (folder-browser-sidebar-<name>).
  • Below the composer: recommended cards (recommended-automation-rail-card-<id>), pinned cards (pinned-automation-card-<id>) and rows (running-automation-row-<id>) with their kebabs; + Add (home-automations-manage).

Before you start

Start with the common launch and health checks, then follow this family’s preconditions in order. Recipes share the fixtures and state named below.

Preconditions:

  • F03.llm-not-configured-banner and F03.composer-blocked-without-llm need a fresh control-openhands launch --new with only control-openhands onboard --skip done (no llm preset yet). Run them first.
  • Everything else: baseline state with control-openhands llm preset deepseek (deepseek-flash active).
  • Workspace fixtures: control-openhands fixture git-repo --name qa-f03-repo and control-openhands fixture folder --name qa-f03-folder (both under <run>/workspace).
  • Automation fixtures (arranged through F22 import and F21 Turn on, not proof): control-openhands fixture file --name qa-f03-pong.automation.json --content '{"version":1,"kind":"automation","spec":{"name":"QA_F03 Pong","trigger":{"type":"cron","schedule":"0 0 1 1 *","timezone":"UTC"},"enabled":true,"prompt":"Reply with the single word: pong. Do not run any tools."}}' and the same with qa-f03-beta / QA_F03 Beta. Run control-openhands browser goto /automations, then for each file run control-openhands browser click 'testid=automations-add-automation', control-openhands browser click 'testid=automations-import-automation', control-openhands browser upload 'testid=automations-import-file' "$OH_VERIFY_RUN/evidence/_fixtures/qa-f03-pong.automation.json", control-openhands browser click 'testid=import-automation-confirm', control-openhands browser wait 'testid=import-automation-modal' --state detached. Imports arrive disabled: turn each on with control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F03 Pong >> role=button[name="Automation actions"]' and control-openhands browser click 'role=list >> has-text=Export >> role=button[name="Turn on"]'. The yearly cron never fires by itself.
  • Blocked here: F03.open-repository-dialog (OpenHands Cloud backend with a connected GitHub/GitLab/Bitbucket provider), F03.isolated-workspace-notice (isolated-runtime backend), F03.workspaces-unsupported (an Agent Server without /api/workspaces), F03.recommended-prompt-launch (a Tavily/Linear/Jira integration installed so some card has nothing missing).

Behavior inventory

39 stable behavior IDs and their expected behavior

Readable recipes

Read each script from top to bottom. Code is copied from the map; prose gives the action, expected observation, and conditions. <id>, <run> and similar placeholders stand for values from your own run. Short forms such as browser count continue the same control-openhands invocation; they are kept as documented.

Expected observations describe the recipe’s contract. Captures below selected recipes show representative real states from this snapshot; they do not mark every mapped behavior as passed. Follow cleanup before moving to another family.

Starting work:

No-LLM banner #

  1. Note
    On a fresh run after onboard --skip, run
  2. Check
    control-openhands browser text 'testid=home-llm-not-configured-banner'
  3. Note
    : Your LLM isn't set up yet, so conversations won't run. Finish setup to get started. and Set up LLM;
  4. Do
    control-openhands browser screenshot --feature F03.llm-not-configured-banner --name banner
  5. Note
    At phone width (control-openhands browser viewport phone),
  6. Check
    control-openhands browser bbox 'testid=home-llm-not-configured-banner'

    is inside the viewport (F03.phone; the banner exists only before llm preset); return with

  7. Do
    control-openhands browser viewport desktop
  8. Check
    control-openhands browser click 'testid=home-llm-not-configured-action' --expect-url '/settings/llm'
  9. Note
    lands on /settings/llm.
  10. Note
    Run the blocked-composer bullet and the draft bullet next (they also need no LLM); only then
  11. Arrange
    control-openhands llm preset deepseek
  12. Do
    control-openhands browser goto /
  13. Check
    control-openhands browser count 'testid=home-llm-not-configured-banner'

    is 0.

The home page shows the composer, Open Workspace and recommended automation cards, with a banner asking for an LLM profile.
A fresh backend needs a model profile before a chat can run. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

The home page shows the composer, Open Workspace and recommended automation cards, with a banner asking for an LLM profile.

Representative documentation capture, not a complete run of this recipe or family. Captured on an isolated local backend at desktop viewport using the current main checkout.

How this screenshot was taken

canvas: 1.26.0 · agent server: 1.53.0 · sdk: 1.53.0 · automation: 1.19.0

OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser goto /
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait testid=home-screen
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser screenshot --feature F03.llm-not-configured-banner --name home-without-profile

Blocked composer #

  1. Note
    Still without an LLM, run
  2. Do
    control-openhands browser goto /

    (the banner's action left /settings/llm),

  3. Do
    control-openhands browser type 'testid=chat-input' 'QA no llm'
  4. Check
    control-openhands browser enabled 'testid=home-chat-launcher >> testid=submit-button'

    (false),

  5. Do
    control-openhands browser press Enter --selector 'testid=chat-input'
  6. Check
    control-openhands browser url

    (still /) and

  7. Check
    control-openhands api GET '/api/conversations/search?limit=5'

    (items is empty).

Draft #

  1. Note
    With text in the home composer,
  2. Do
    control-openhands browser reload
  3. Check
    control-openhands browser text 'testid=chat-input'

    returns the same text;

  4. Do
    control-openhands browser storage --session

    lists oh:home-prompt-draft.

  5. Note
    That the draft clears after a successful launch is checked in the first launch (Launch without a folder).

Entry points #

  1. Do
    control-openhands browser goto /conversations
  2. Check
    control-openhands browser count 'testid=home-chat-launcher'

    is 1 (same for /).

  3. Note
    From /settings/secrets (control-openhands browser goto /settings/secrets),
  4. Do
    control-openhands browser press Control+k
  5. Do
    control-openhands browser type 'testid=command-menu >> role=combobox' 'New chat'
  6. Check
    control-openhands browser snapshot 'testid=command-menu'

    (one option New chat Start a fresh OpenHands conversation. Go [selected]),

  7. Do
    control-openhands browser press Enter
  8. Check
    control-openhands browser url
  9. Note
    : /conversations.
  10. Note
    From /settings/secrets (control-openhands browser goto /settings/secrets again),
  11. Check
    control-openhands browser click 'testid=sidebar-conversations-link' --expect-url '/conversations(\?|$)'
  12. Note
    and browser count 'testid=home-chat-launcher' is 1.

/model on home #

  1. Check
    control-openhands api GET /api/profiles

    shows active_profile deepseek-flash.

  2. Do
    control-openhands browser goto /

    (Entry points ended on /conversations).

  3. Expect
    The draft from the earlier bullets is still in the composer: clear it with
  4. Do
    control-openhands browser fill 'testid=chat-input' ''

    (typing would append).

  5. Do
    control-openhands browser type 'testid=chat-input' '/model deepseek-pro'
  6. Do
    control-openhands browser press Escape --selector 'testid=chat-input'

    (closes the slash menu),

  7. Do
    control-openhands browser press Enter --selector 'testid=chat-input'
  8. Expect
    The URL stays /, chat-input is empty, the composer's model pill (browser text 'testid=chat-input-llm-profile') reads deepseek-pro, api GET /api/profiles shows active_profile deepseek-pro, and api GET '/api/conversations/search?limit=20' lists no new conversation.
  9. Note
    Restore with the same steps and /model deepseek-flash.

Launch without a folder #

  1. Do
    control-openhands browser goto /conversations

    (no workspace is picked yet; the pill reads Open Workspace), then fill chat-input with Reply with only the word ok. and click testid=home-chat-launcher >> testid=submit-button with --expect-url '/conversations/[^/]+$'.

  2. Wait
    control-openhands conversation wait <id> --timeout 180
  3. Note
    reports workspace <run>/private/home/workspace/project/<id without dashes>.
  4. Expect
    This is the first successful launch:
  5. Check
    control-openhands browser click 'testid=sidebar-conversations-link' --expect-url '/conversations$'

    shows an empty chat-input and browser storage --session no longer lists oh:home-prompt-draft.

Plugins #

  1. Do
    control-openhands browser goto /conversations

    (a fresh page),

  2. Do
    control-openhands browser click 'testid=open-plugin-picker'
  3. Wait
    control-openhands browser wait 'testid=plugin-picker-loading' --state hidden --timeout 60000
  4. Note
    cards plugin-picker-card-<name> are listed.
  5. Do
    control-openhands browser fill 'testid=plugin-picker-search-input' weather
  6. Note
    leaves one card (control-openhands browser count '[data-testid^="plugin-picker-card-"]' is 1); zzz-none makes browser text 'testid=plugin-picker' read No plugins available. With weather again,
  7. Do
    control-openhands browser click 'testid=plugin-picker-toggle-city-weather' --hover-first 150
  8. Note
    : the footer reads 1 selected and
  9. Check
    control-openhands browser attr 'testid=plugin-picker-toggle-city-weather' aria-label

    is Remove.

  10. Do
    control-openhands browser click 'testid=plugin-picker-done'
  11. Check
    control-openhands browser text 'testid=plugin-picker-count'

    is 1; reopening keeps the switch checked, and testid=plugin-picker-close also closes it.

  12. Note
    On hover-swapping cards, use --hover-first 150 for subsequent Add/Remove clicks too and assert both the label and aria-checked; the shared index gotcha explains why a plain mouse click can be swallowed (including at phone width).

Plugin reaches the agent #

  1. Note
    With city-weather attached, start the no-folder launch above without its goto (a page load drops the attached plugin), then
  2. Check
    control-openhands api GET '/api/conversations/<id>/events/search?limit=20'
  3. Note
    : the system prompt contains city-weather:now (a conversation started without the plugin has none).
  4. Note
    Back on /conversations (control-openhands browser goto /conversations),
  5. Check
    control-openhands browser count 'testid=plugin-picker-count'

    is 0.

Creation failure #

  1. Note
    Fill chat-input with QA error path, run
  2. Do
    control-openhands service stop agent-server
  3. Note
    then clear the observation window with
  4. Check
    control-openhands browser toasts --clear
  5. Note
    Start genuine temporal evidence before submitting:
  6. Do
    control-openhands browser record start --feature F03.create-error-toast --name failed-launch
  7. Do
    control-openhands browser click 'testid=home-chat-launcher >> testid=submit-button' --observe 'role=status' --observe-ms 500
  8. Note
    then immediately
  9. Check
    control-openhands browser screenshot --feature F03.create-error-toast --name toasts

    (this early still may show the loading state).

  10. Note
    Keep recording and wait for the real error with
  11. Wait
    control-openhands browser wait-text 'HTTP request failed (502 Bad Gateway)' --timeout 15000
  12. Wait
    control-openhands browser wait 'role=status >> has-text=Creating conversation…' --state hidden --timeout 15000
  13. Note
    browser toasts and browser toasts --history show Creating conversation… replaced by exactly one error toast, such as HTTP request failed (502 Bad Gateway): "Bad Gateway: connect ECONNREFUSED 127.0.0.1:<port+1>" (transport detail is preserved by design, #17985).
  14. Expect
    Count every new error toast in this window; an additional toast is a failure to record, not one to discard. browser text 'testid=chat-input' still reads QA error path and browser enabled 'testid=home-chat-launcher >> testid=submit-button' is true.
  15. Note
    Stop the recording with
  16. Do
    control-openhands browser record stop
  17. Note
    inspect its loading→error transition and recovered prompt, paired with the full screenshot.
  18. Note
    Bring the stack back with
  19. Do
    control-openhands restart
  20. Do
    control-openhands browser reload
  21. Note
    the prompt remains QA error path.

Workspaces:

Folder browser #

  1. Do
    control-openhands browser click 'testid=open-workspace-button'
  2. Note
    before any workspace is picked,
  3. Check
    control-openhands browser snapshot 'role=dialog'

    shows Open Workspace, combobox "Select a workspace" and button "Confirm" [disabled] (F03.open-workspace-dialog).

  4. Do
    control-openhands browser click 'testid=workspace-dropdown'
  5. Note
    with no saved workspace,
  6. Check
    control-openhands browser text 'testid=workspace-dropdown-empty'

    is No workspaces yet, above + Add Workspace (F03.workspace-dropdown-search; browser screenshot --feature F03.workspace-dropdown-search --name empty-list).

  7. Do
    control-openhands browser click 'testid=add-workspaces-button'
  8. Check
    control-openhands browser text 'testid=folder-browser-current-path'

    is <run>/private/home.

  9. Do
    control-openhands browser click 'testid=folder-browser-up'
  10. Note
    twice (now <run>) and
  11. Do
    control-openhands browser click 'testid=folder-browser-entry-workspace'
  12. Check
    control-openhands browser text 'testid=folder-browser-list'

    lists qa-f03-folder and qa-f03-repo as Folder (browser screenshot --feature F03.folder-browser --name workspace-dir).

  13. Note
    Then, reopening the browser each time with browser click 'testid=workspace-dropdown' and browser click 'testid=add-workspaces-button' (the Open Workspace dialog stays open behind it; the browser always restarts at <run>/private/home, so repeat Up twice and folder-browser-entry-workspace):

Alternative paths (repeat the preparation above for each):

    1. Do
      control-openhands browser click 'testid=folder-browser-cancel'
    2. Note
      : browser count 'testid=folder-browser-modal' is 0 and the selection is unchanged.
    1. Do
      control-openhands browser click 'testid=folder-browser-add-all-subdirs'
    2. Note
      : the browser closes; after browser click 'testid=workspace-dropdown' the dropdown offers qa-f03-folder and qa-f03-repo (browser snapshot 'role=dialog': option "qa-f03-folder", option "qa-f03-repo", button "Manage Workspaces").
    1. Do
      control-openhands browser click 'testid=folder-browser-use'

      (Add this directory) at <run>/workspace:

    2. Check
      control-openhands browser value 'testid=workspace-dropdown'

      is workspace (auto-selected).

Favorites and locations #

  1. Note
    With the browser open,
  2. Check
    control-openhands browser testids 'testid=folder-browser-sidebar'

    lists folder-browser-sidebar-home, folder-browser-sidebar-/ and, after any no-folder conversation, folder-browser-sidebar-workspace.

  3. Do
    control-openhands browser click 'testid=folder-browser-sidebar-/'
  4. Note
    makes browser text 'testid=folder-browser-current-path' read / and
  5. Check
    control-openhands browser enabled 'testid=folder-browser-up'
  6. Note
    false; folder-browser-sidebar-workspace goes to <run>/private/home/workspace and folder-browser-sidebar-home back to <run>/private/home.
  7. Note
    Close with testid=folder-browser-cancel.

Open Workspace #

  1. Expect
    The dialog is still open from the previous bullets, with workspace selected (its disabled Confirm before a pick is checked at the start of Folder browser).
  2. Do
    control-openhands browser click 'testid=workspace-dropdown'
  3. Do
    control-openhands browser click 'testid=workspace-dropdown-menu >> role=option[name="qa-f03-repo"]'
  4. Check
    control-openhands browser enabled 'testid=workspace-launch-button'

    (true),

  5. Do
    control-openhands browser click 'testid=workspace-launch-button'
  6. Expect
    The dialog closes and
  7. Check
    control-openhands browser text 'testid=home-git-control-bar-preview'

    is qa-f03-repo and Local Repo (browser screenshot --feature F03.open-workspace-dialog --name preview-bar).

  8. Do
    control-openhands browser click 'testid=home-git-control-bar-preview >> role=button[name*="qa-f03-repo"]'
  9. Note
    reopens the dialog with browser value 'testid=workspace-dropdown' qa-f03-repo;
  10. Do
    control-openhands browser click 'testid=close-open-workspace-dialog'
  11. Note
    closes it, and so does
  12. Do
    control-openhands browser press Escape
Open Workspace dialog with an empty Select a workspace field and disabled Confirm button.
Choose a workspace or add a folder before confirming the selection. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Open Workspace dialog with an empty Select a workspace field and disabled Confirm button.

Representative documentation capture, not a complete run of this recipe or family. Captured on an isolated local backend at desktop viewport using the current main checkout.

How this screenshot was taken

canvas: 1.26.0 · agent server: 1.53.0 · sdk: 1.53.0 · automation: 1.19.0

OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser goto /
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait testid=home-screen
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click testid=open-workspace-button
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait role=dialog
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser screenshot role=dialog --feature F03.open-workspace-dialog --name open-workspace

Launch in a folder #

  1. Note
    Open Workspace (above) left qa-f03-repo confirmed in the preview bar.
  2. Do
    control-openhands browser fill 'testid=chat-input' 'Run pwd and reply with only its output.'
  3. Do
    control-openhands browser click 'testid=home-chat-launcher >> testid=submit-button' --observe 'text=Creating conversation' --expect-url '/conversations/[^/]+$'
  4. Note
    observed shows Creating conversation… then <absent>; the returned URL holds <id>.
  5. Wait
    control-openhands conversation wait <id> --timeout 180
  6. Note
    reports workspace <run>/workspace/qa-f03-repo, and
  7. Check
    control-openhands conversation events <id> --kinds ObservationEvent,MessageEvent

    shows the terminal observation and the reply <run>/workspace/qa-f03-repo.

Search, groups and empty #

  1. Do
    control-openhands browser goto /

    (Launch in a folder left its conversation),

  2. Do
    control-openhands browser click 'testid=open-workspace-button'
  3. Do
    control-openhands browser click 'testid=workspace-dropdown'
  4. Note
    With the dropdown open,
  5. Do
    control-openhands browser fill 'testid=workspace-dropdown' repo
  6. Check
    control-openhands browser text 'testid=workspace-dropdown-menu'

    is qa-f03-repo.

  7. Note
    With the parent and the standalone workspace saved, the menu text is workspace, qa-f03-folder, qa-f03-repo, Other, workspace and
  8. Check
    control-openhands browser count 'testid=workspace-group-header'

    is 2 (--name grouped).

  9. Do
    control-openhands browser fill 'testid=workspace-dropdown' zzz-none

    shows the empty state:

  10. Check
    control-openhands browser text 'testid=workspace-dropdown-empty'

    is No workspaces yet (--name no-match) and

  11. Check
    control-openhands browser count 'testid=add-workspaces-button'

    is still 1.

  12. Note
    Clear with
  13. Do
    control-openhands browser fill 'testid=workspace-dropdown' ''
  14. Note
    browser count 'testid=workspace-dropdown-empty' is 0 again.

Reload #

  1. Expect
    After confirming qa-f03-repo,
  2. Do
    control-openhands browser reload
  3. Check
    control-openhands browser count 'testid=home-git-control-bar-preview'

    is 0 and the pill is back.

  4. Note
    Click it;
  5. Check
    control-openhands browser value 'testid=workspace-dropdown'

    is qa-f03-repo (sessionStorage oh:home-selected-workspace-path).

  6. Expect
    An unconfirmed pick persists the same way:
  7. Do
    control-openhands browser click 'testid=workspace-dropdown'
  8. Do
    control-openhands browser click 'testid=workspace-dropdown-menu >> role=option[name="qa-f03-folder"]'

    (value qa-f03-folder), then

  9. Do
    control-openhands browser click 'testid=close-open-workspace-dialog'

    (never Confirm or Launch): the preview count stays 0 and

  10. Do
    control-openhands browser storage --session --values

    shows oh:home-selected-workspace-path ending in /workspace/qa-f03-folder.

  11. Note
    Leave and come back with
  12. Do
    control-openhands browser goto /settings

    (lands on /settings/agents, browser count 'testid=home-screen' 0) and

  13. Do
    control-openhands browser goto /conversations
  14. Note
    click open-workspace-button: browser value 'testid=workspace-dropdown' is qa-f03-folder (browser screenshot --feature F03.selection-after-reload --name unconfirmed-pick-restored).
  15. Note
    Close with the X,
  16. Do
    control-openhands browser reload
  17. Note
    click the pill again: the value is still qa-f03-folder and browser enabled 'testid=workspace-launch-button' is true.
  18. Note
    Restore the state the next bullets start from with browser click 'testid=workspace-dropdown',
  19. Do
    control-openhands browser click 'testid=workspace-dropdown-menu >> role=option[name="qa-f03-repo"]'

    (value qa-f03-repo, the storage value ends in /workspace/qa-f03-repo) and

  20. Do
    control-openhands browser click 'testid=workspace-launch-button'
  21. Note
    : the dialog closes and
  22. Check
    control-openhands browser text 'testid=home-git-control-bar-preview'

    is qa-f03-repo and Local Repo again.

Workspace mode #

  1. Note
    With the preview bar showing,
  2. Do
    control-openhands browser click 'testid=workspace-mode-selector'
  3. Check
    control-openhands browser snapshot 'testid=workspace-mode-selector-menu'

    (menuitemradio "Local Repo" [checked], menuitemradio "New Worktree"),

  4. Do
    control-openhands browser click 'testid=workspace-mode-selector-option-new_worktree'
  5. Note
    browser storage --values shows openhands-last-local-workspace-mode new_worktree; after browser reload and reconfirming the workspace, browser text 'testid=home-git-control-bar-preview' reads qa-f03-repo and New Worktree.
  6. Note
    Restore with testid=workspace-mode-selector-option-local_repo.

Manage #

  1. Note
    With the parent and workspace saved:
  2. Do
    control-openhands browser click 'testid=workspace-dropdown'
  3. Do
    control-openhands browser click 'testid=manage-workspaces-button'
  4. Check
    control-openhands browser testids 'testid=manage-workspaces-modal'

    shows manage-workspaces-row-workspace, manage-workspaces-parent-row-workspace, manage-workspaces-child-qa-f03-folder and manage-workspaces-child-qa-f03-repo.

  5. Do
    control-openhands browser click 'testid=manage-workspaces-remove-workspace'
  6. Check
    control-openhands browser text 'testid=confirmation-modal'

    reads Remove "workspace" from your saved workspaces?.

  7. Do
    control-openhands browser click 'testid=confirmation-modal >> testid=cancel-button'
  8. Note
    keeps the row (browser count 'testid=manage-workspaces-row-workspace' is 1); repeat Remove and
  9. Do
    control-openhands browser click 'testid=confirmation-modal >> testid=confirm-button'
  10. Note
    : the count is 0.
  11. Note
    Removing a workspace clears the dialog's selection only when it was the selected one: with qa-f03-repo selected,
  12. Do
    control-openhands browser click 'testid=manage-workspaces-done'
  13. Note
    leaves browser value 'testid=workspace-dropdown' at qa-f03-repo.
  14. Expect
    After browser reload the dropdown lists only the two children, and
  15. Check
    control-openhands api GET /api/workspaces

    shows workspaces: [] plus the parent.

  16. Note
    Cleanup proves Remove parent:
  17. Do
    control-openhands browser click 'testid=manage-workspaces-remove-parent-workspace'

    reads Remove "workspace" and its 2 child workspaces?; Confirm leaves browser text 'testid=manage-workspaces-list' at You don't have any workspaces yet. and api GET /api/workspaces empty; because the selected qa-f03-repo belonged to that parent, after browser click 'testid=manage-workspaces-done' browser value 'testid=workspace-dropdown' is empty and browser enabled 'testid=workspace-launch-button' is false.

Automations on home (fixtures imported and turned on):

Recommended rail #

  1. Note
    On / after browser reload,
  2. Check
    control-openhands browser attr 'testid=recommended-automations-rail-fade-left' data-visible

    is false and ...-fade-right is true;

  3. Do
    control-openhands browser scroll 'testid=recommended-automation-rail-card-github-agents-md-maintainer'
  4. Note
    turns the left fade true (browser screenshot --feature F03.recommended-automations-rail --name scrolled-end).
  5. Check
    control-openhands browser click 'testid=recommended-automation-rail-card-custom-automation' --expect-url 'automations/new'
  6. Note
    lands on /automations/new/custom-automation (github-pr-reviewer, github-issue-to-pr and news-digest do the same).

Responder choice #

  1. Note
    From /conversations (control-openhands browser goto /conversations; the rail click left /automations/new/...),
  2. Do
    control-openhands browser click 'testid=recommended-automation-rail-card-github-agents-md-maintainer'
  3. Check
    control-openhands browser snapshot 'testid=responder-deployment-modal'

    shows Choose how your responder should run, Continue with local setup and Open OpenHands Cloud integrations (slack-channel-monitor too).

  4. Do
    control-openhands browser click 'testid=responder-deployment-continue-local'
  5. Note
    saves an OPENHANDS_URL secret and opens the GitHub MCP install dialog (browser snapshot 'role=dialog': dialog "GitHub", Personal access token);
  6. Do
    control-openhands browser click 'role=dialog >> role=button[name="Cancel"]'
  7. Note
    closes it on /conversations.
  8. Note
    Delete OPENHANDS_URL afterwards in Settings → Secrets (F14):
  9. Do
    control-openhands browser goto /settings/secrets
  10. Do
    control-openhands browser click 'testid=secret-item >> has-text=OPENHANDS_URL >> testid=delete-secret-button'
  11. Do
    control-openhands browser click 'role=dialog >> role=button[name="Confirm"]'

Cloud option #

  1. Note
    From /conversations (control-openhands browser goto /conversations),
  2. Do
    control-openhands browser click 'testid=recommended-automation-rail-card-slack-channel-monitor'
  3. Do
    control-openhands browser click 'testid=responder-deployment-open-openhands-cloud'
  4. Note
    : browser count 'testid=responder-deployment-modal' is 0,
  5. Do
    control-openhands browser tabs

    lists a second page https://app.all-hands.dev/settings/integrations (it can take a second to appear: rerun tabs before concluding) (close it with control-openhands browser close-tab 1), and api GET /api/settings/secrets has no OPENHANDS_URL.

  6. Note
    Reopen the card and
  7. Do
    control-openhands browser click 'testid=responder-deployment-modal-close'
  8. Note
    : the dialog is gone and nothing else opens.

Missing integration #

  1. Do
    control-openhands browser click 'testid=recommended-automation-rail-card-research-brief-writer'
  2. Note
    opens dialog "Tavily" (Command npx -y tavily-mcp, Tavily API key); Cancel closes it and no conversation is created.

Already added #

  1. Check
    control-openhands browser count 'testid=recommended-automation-rail-card-news-digest'

    is 1.

  2. Note
    Import qa-f03-news.automation.json (same fixture recipe as Preconditions, with "name":"Daily news digest"; it stays disabled), browser goto /, browser wait 'testid=recommended-automation-rail-card-custom-automation': the news-digest count is 0.
  3. Note
    Delete it on /automations (control-openhands browser goto /automations, then the Cleanup recipe below with has-text=Daily news digest); back on / (control-openhands browser goto /) the count is 1 again.

Activity list #

  1. Wait
    control-openhands browser wait '[data-testid^="running-automation-row-"]'
  2. Check
    control-openhands browser text 'testid=running-automations-list'
  3. Note
    : both fixtures with 0 0 1 1 * and No activity yet (browser screenshot --feature F03.home-automations-list --name list); no row has a sparkline link yet (the run-bar link is checked after the first run, in the next bullet).
  4. Do
    control-openhands browser click '[data-testid^="running-automation-row-"] >> has-text=QA_F03 Beta >> role=link' --expect-url '/automations/[^/?]+'
  5. Note
    opens the detail page; after
  6. Do
    control-openhands browser goto /
  7. Check
    control-openhands browser click 'testid=home-automations-manage' --expect-url '/automations$'
  8. Note
    opens the dashboard.

Row menu, Run now and Cancel #

  1. Do
    control-openhands browser goto /

    (the Manage link left the dashboard), then

  2. Do
    control-openhands browser click '[data-testid^="running-automation-row-"] >> has-text=QA_F03 Pong >> [data-testid^="running-automation-menu-"]'
  3. Check
    control-openhands browser snapshot '[data-testid^="running-automation-menu-panel-"]'
  4. Note
    : Run now, View, Edit, Turn off, separator, Pin to dashboard.
  5. Check
    control-openhands browser click '[data-testid^="running-automation-run-"]' --observe 'role=status'
  6. Note
    observes Automation dispatched.
  7. Note
    Reopen the menu: Run now [disabled] and Cancel run.
  8. Do
    control-openhands browser click '[data-testid^="running-automation-cancel-"]'
  9. Check
    control-openhands browser toasts

    reads Automation run cancelled;

  10. Check
    control-openhands api GET /api/automation/v1/<automation-id>/runs

    (id from browser testids --filter running-automation-row) shows CANCELLED, and after browser reload the row reads Just now · Cancelled.

  11. Note
    Pong now has a run:
  12. Do
    control-openhands browser click '[data-testid^="running-automation-activity-"] >> role=link' --expect-url '/automations/[^/?]+\?run='
  13. Note
    opens it; return with
  14. Do
    control-openhands browser goto /

Tooltip #

  1. Do
    control-openhands browser tooltip '[data-testid^="running-automation-row-"] >> has-text=QA_F03 Pong'

    returns QA_F03 Pong, Trigger 0 0 1 1 *, Status Cancelled, Last run Just now, Task Cancelled by user.

View and Edit #

  1. Note
    From the Beta row menu,
  2. Check
    control-openhands browser click '[data-testid^="running-automation-view-"]' --expect-url '/automations/[^/?]+$'
  3. Note
    opens the detail page.
  4. Note
    Back on / (control-openhands browser goto /), reopen the menu and click [data-testid^="running-automation-edit-"]: the Edit automation modal shows browser value 'label=Name' QA_F03 Beta.
  5. Do
    control-openhands browser fill 'label=Prompt' 'Reply with the single word: beta2. Do not run any tools.'
  6. Check
    control-openhands browser click 'testid=edit-automation-save' --observe 'role=status'

    (Automation updated); api GET /api/automation/v1/<automation-id> shows the new prompt.

  7. Expect
    A timeout above 1800 is refused client-side with Timeout cannot exceed 1800 seconds.

Turn off #

  1. Note
    From the Beta row menu click [data-testid^="running-automation-turn-off-"];
  2. Check
    control-openhands browser snapshot 'role=dialog'

    reads Turn off "QA_F03 Beta"? It will stop running on its schedule until you turn it back on. with Cancel and Turn off.

  3. Do
    control-openhands browser click 'role=dialog >> role=button[name="Cancel"]'
  4. Note
    keeps it enabled; repeat and
  5. Do
    control-openhands browser click 'role=dialog >> role=button[name="Turn off"]'
  6. Note
    browser reload:
  7. Check
    control-openhands browser count '[data-testid^="running-automation-row-"] >> has-text=QA_F03 Beta'

    is 0 and the API shows enabled: false.

  8. Note
    Turn it back on from /automations (Preconditions) for the pin checks.

Pin #

  1. Do
    control-openhands browser goto /

    (turning Beta back on left /automations); from Pong's row menu click [data-testid^="running-automation-pin-"];

  2. Check
    control-openhands browser text 'testid=pinned-automations-dashboard'

    shows Pinned automations, QA_F03 Pong, the prompt, cron and the last status; browser storage --values holds the id under oh:home-pinned-automations:<backend>:-.

  3. Note
    Pin Beta too;
  4. Do
    control-openhands browser eval "[...document.querySelectorAll('[data-testid^=pinned-automation-card-]')].map(e=>e.innerText.split('\n')[0]).join(',')"

    is QA_F03 Pong,QA_F03 Beta.

  5. Do
    control-openhands browser click '[data-testid^="pinned-automation-card-"] >> role=link[name="QA_F03 Pong"]' --expect-url '/automations/[^/?]+$'
  6. Note
    opens the detail page.

Pinned menu #

  1. Do
    control-openhands browser goto /

    (the Pin bullet ends on Pong's detail page), then

  2. Do
    control-openhands browser click '[data-testid^="pinned-automation-card-"] >> has-text=QA_F03 Beta >> [data-testid^="pinned-automation-menu-"]'

    lists Run now, View, Edit, Turn off and Unpin from dashboard. [data-testid^="pinned-automation-run-"] toasts Automation dispatched; reopen and [data-testid^="pinned-automation-cancel-"] toasts Automation run cancelled (the run is CANCELLED).

  3. Note
    Reopen and click [data-testid^="unpin-automation-"]; after browser reload only QA_F03 Pong is pinned and Beta stays in the list.

Pinned links #

  1. Note
    With Pong pinned and run once,
  2. Do
    control-openhands browser click '[data-testid^="pinned-automation-card-"] >> has-text=QA_F03 Pong >> [data-testid^="pinned-automation-activity-"] >> role=link' --expect-url '/automations/[^/?]+\?run='
  3. Note
    opens the run.
  4. Note
    Back on / (control-openhands browser goto /),
  5. Do
    control-openhands browser focus '[data-testid^="pinned-automation-card-"] >> role=link[name="QA_F03 Beta"]'
  6. Do
    control-openhands browser press Enter
  7. Wait
    control-openhands browser wait-url '/automations/[^/?]+$'
  8. Note
    opens Beta's detail page (pin Beta again first if it was unpinned).

Reorder #

  1. Do
    control-openhands browser goto /

    (Pinned links ends on Beta's detail page).

  2. Note
    With Pong and Beta pinned (order QA_F03 Pong,QA_F03 Beta, side by side at desktop), read the Beta handle and Pong card boxes with
  3. Check
    control-openhands browser bbox '[data-testid^="pinned-automation-card-"] >> has-text=QA_F03 Beta >> [data-testid^="pinned-automation-drag-"]'
  4. Check
    control-openhands browser bbox '[data-testid^="pinned-automation-card-"] >> has-text=QA_F03 Pong'
  5. Note
    then drag the handle onto the left fifth of Pong's card:
  6. Do
    control-openhands browser drag '[data-testid^="pinned-automation-card-"] >> has-text=QA_F03 Beta >> [data-testid^="pinned-automation-drag-"]' --by -522,0 --steps 20

    (at 1440×1000 the handle centre is x≈1065 and Pong's card spans x 486–864).

  7. Expect
    The order eval from the Pin bullet reads QA_F03 Beta,QA_F03 Pong, oh:home-pinned-automations:<backend>:- lists Beta's id first, and the order survives browser reload.
  8. Note
    Drag Pong's handle the same way to restore.
  9. Expect
    The target form browser drag <handle> <card> [--position before] does not reorder (the drop lands before the drag-over position is committed).

Backend down #

  1. Do
    control-openhands service stop automation
  2. Do
    control-openhands browser reload
  3. Note
    then browser count on testid=running-automations-list, testid=pinned-automations-dashboard and testid=recommended-automations-rail: all 0, no toast, composer usable.
  4. Do
    control-openhands restart
  5. Note
    and browser reload bring them back.

Phone and cleanup:

Phone #

  1. Do
    control-openhands browser viewport phone
  2. Check
    control-openhands browser bbox 'testid=pinned-automations-dashboard'
  3. Note
    and ... 'testid=running-automations-list' (358 px wide, pageHorizontalOverflow false), browser screenshot --feature F03.phone --name automations (the banner was checked at phone width in the No-LLM banner bullet).
  4. Note
    Open Workspace:
  5. Do
    control-openhands browser click 'testid=open-workspace-button'
  6. Note
    then browser bbox 'testid=open-workspace-dialog-body' is inside the viewport.
  7. Note
    Folder browser: arrange a long name first with
  8. Arrange
    control-openhands fixture folder --name qa-f03-folder-with-a-name-too-long-for-a-phone

    (it lands in <run>/workspace), then

  9. Do
    control-openhands browser click 'testid=workspace-dropdown'
  10. Do
    control-openhands browser click 'testid=add-workspaces-button'
  11. Do
    control-openhands browser eval "(()=>{const b=document.querySelector('[data-testid=folder-browser-entry-workspace] span span.truncate');return b.getBoundingClientRect().width})()"

    is greater than 0 (about 67).

  12. Do
    control-openhands browser click 'testid=folder-browser-up'
  13. Note
    twice (now <run>) and
  14. Do
    control-openhands browser click 'testid=folder-browser-entry-workspace'
  15. Do
    control-openhands browser eval "(()=>{const b=document.querySelector('[data-testid=folder-browser-entry-qa-f03-folder-with-a-name-too-long-for-a-phone] span span.truncate');return [b.scrollWidth>b.clientWidth,getComputedStyle(b).textOverflow]})()"
  16. Note
    : [true,"ellipsis"] (about 109 of 293 px shown).
  17. Do
    control-openhands browser screenshot --feature F03.phone --name folder-names

    shows each name beside Folder under separate Name and Kind headers, the long one cut to qa-f03-folder-w….

  18. Note
    Close with
  19. Do
    control-openhands browser click 'testid=folder-browser-cancel'
  20. Do
    control-openhands browser click 'testid=close-open-workspace-dialog'
  21. Note
    then return with
  22. Do
    control-openhands browser viewport desktop

Cleanup #

  1. Note
    Delete both fixtures on /automations (control-openhands browser goto /automations; F21: kebab role=list >> has-text=Export >> role=button[name="Delete"], then role=heading[name="Delete automation"] >> xpath=.. >> role=button[name="Delete"]).
  2. Note
    On / (control-openhands browser goto /), browser count 'testid=pinned-automations-dashboard' is 0 and browser text 'testid=running-automations-empty-hint' is No automations yet. Add one to automate repetitive work.

Gotchas and known limits

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

Source paths: src/routes/home.tsx, src/routes/index-home.tsx, src/components/features/home/, src/components/features/plugins/plugin-picker-modal.tsx, src/components/features/automations/recommended-automations-launcher.tsx, src/hooks/use-home-pinned-automations.ts, src/hooks/use-home-automation-actions.ts, src/utils/recommended-automation-rail.ts, src/components/features/automations/responder-deployment-modal.tsx.