EN / field notes OpenHands feature map

OpenHands / F07

Conversation page, header and management menu

The conversation page (/conversations/<id>) shows the chat pane: a header with the runtime status dot, the editable title, the "..." management menu and the Git actions, Overview and panel toggles, then the message history and the composer. From the header a user renames, stops or resumes the runtime, opens the skills, hooks and agent-tools dialogs, exports or downloads the conversation, jumps to usage and deletes it.

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

How to get to it

  • Sidebar conversation row (conversation-card), or the home composer submit, which navigates to the new conversation.
  • Direct URL /conversations/<id>.
  • Direct URL /conversations/<id>/panel, or the header panel button, at phone width (up to 1024 px); at desktop width that URL redirects to /conversations/<id> with the drawer open.
  • Hovering a message in the history: Branch from here (local backends only).
  • Chips under the composer (git control bar) for conversations in a git workspace (home Open Workspace).
  • Header controls inside testid=chat-pane-header: the status dot (server-status-menu-trigger), the title (conversation-name-title), the "..." button (ellipsis-button, aria "More options"), conversation-git-actions-toggle, conversation-overview-toggle, right-panel-toggle.

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:

  • Baseline state (launched, doctored, onboard --skip done) and an active LLM profile (control-openhands llm preset deepseek).
  • A finished conversation: control-openhands conversation start --prompt "Reply with the single word: pong" --wait --timeout 240 prints its <id>.
  • A running conversation for the active-only items: control-openhands conversation start --prompt "Run the shell command: sleep 90 ; then reply with the word done" --wait --until running --timeout 120 prints <running-id>. It finishes after about 90 s, so start it right before F07.stop-confirm (after a Stop it stays paused until Start Runtime).
  • A conversation in a git workspace for the git control bar: control-openhands fixture git-repo --name qa-f07-repo, then control-openhands browser goto / (conversation start left the browser on <id>), control-openhands browser click 'testid=open-workspace-button', control-openhands browser click 'role=dialog >> role=combobox[name="Select a workspace"]', control-openhands browser click 'role=dialog >> role=button[name="+ Add Workspace"]', control-openhands browser click 'testid=folder-browser-sidebar-workspace', control-openhands browser click 'testid=folder-browser-up' three times (to the run directory), control-openhands browser click 'testid=folder-browser-entry-workspace', control-openhands browser click 'testid=folder-browser-entry-qa-f07-repo', control-openhands browser click 'testid=folder-browser-use', control-openhands browser click 'testid=workspace-launch-button', then control-openhands conversation start --prompt "Reply with the single word: ok" --stay --wait --timeout 240 prints <git-id>.
  • Cloud-only rows (F07.cloud-only) are blocked without an OpenHands Cloud account.
  • Desktop viewport unless a bullet says otherwise.

Behavior inventory

25 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.

Open by URL #

  1. Do
    control-openhands browser goto /conversations/<id>
  2. Wait
    control-openhands browser wait 'testid=chat-pane-header'
  3. Check
    control-openhands browser text 'testid=conversation-name-title'
  4. Check
    control-openhands browser count 'testid=chat-interface >> testid=agent-message'
  5. Expect
    The title is the generated one and the count is 1.
  6. Do
    control-openhands browser eval "localStorage.getItem('openhands-last-conversation-by-backend')"

    reads {"default-local":"<id>"}.

Unknown id #

  1. Do
    control-openhands browser goto /conversations/00000000-0000-0000-0000-000000000000
  2. Wait
    control-openhands browser wait-url '/conversations$'
  3. Check
    control-openhands browser toasts
  4. Expect
    A toast reads This conversation does not exist, or you do not have permission to access it. If this is your conversation, try switching to the workspace where it was created. and the last-conversation slot is {}.
  5. Expect
    It is the only toast:
  6. Check
    control-openhands browser toasts --history

    has no entry with does not understand.

Rename inline #

  1. Note
    On /conversations/<id> (control-openhands browser goto /conversations/<id>; the unknown id redirected to /conversations) run
  2. Do
    control-openhands browser dblclick 'testid=conversation-name-title'
  3. Do
    control-openhands browser fill 'testid=conversation-name-input' 'QA_F07 renamed'
  4. Do
    control-openhands browser press Enter
  5. Check
    control-openhands browser toasts

    (Conversation title updated successfully), then

  6. Do
    control-openhands browser reload
  7. Check
    control-openhands browser text 'testid=conversation-name-title'
  8. Expect
    It reads QA_F07 renamed, and so does
  9. Check
    control-openhands browser text 'testid=conversation-card >> testid=conversation-card-title'

Rename from the menu, empty value #

  1. Do
    control-openhands browser click 'testid=chat-pane-header >> testid=ellipsis-button'
  2. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=rename-button'
  3. Do
    control-openhands browser fill 'testid=conversation-name-input' ''
  4. Do
    control-openhands browser press Enter
  5. Expect
    The title stays QA_F07 renamed and no toast appears.

History skeleton and sidebar entry #

  1. Note
    From / (control-openhands browser goto /) run
  2. Do
    control-openhands browser click 'testid=conversation-card >> has-text=QA_F07 renamed' --expect-url '/conversations/[0-9a-f-]+' --observe 'testid=chat-messages-skeleton' --observe-ms 3000

    (run it after the rename above; before it, match the generated title instead).

  3. Expect
    The observed list shows the skeleton present for a few hundred ms, then <absent>; the URL is /conversations/<id>?backend=default-local.

Status menu, stop #

  1. Note
    On /conversations/<id> run
  2. Check
    control-openhands browser hover 'testid=server-status-menu-trigger'

    (for a mouse the hover opens the menu and a click on the open menu's dot closes it; a CLI click from elsewhere usually opens it only because the click lands before the hover renders, a race that sometimes leaves it closed, so do not click),

  3. Check
    control-openhands browser text 'testid=server-status-context-menu'

    (Running / Stop Runtime), then

  4. Check
    control-openhands browser click 'testid=server-status-context-menu >> testid=stop-server-button' --expect-url '/conversations$'
  5. Check
    control-openhands browser toasts
  6. Expect
    The toasts read Stopping conversation... and Conversation stopped and the URL is /conversations.

Menu #

  1. Note
    Stop Runtime left the browser on /conversations: run
  2. Do
    control-openhands browser goto /conversations/<git-id>

    (a finished conversation whose runtime was not stopped), then

  3. Do
    control-openhands browser click 'testid=chat-pane-header >> testid=ellipsis-button'
  4. Check
    control-openhands browser testids 'testid=conversation-name-context-menu'
  5. Note
    On a finished or running conversation the items are rename-button, show-skills-button, show-hooks-button, show-agent-tools-button, export-transcript-button (Export…), download-trajectory-button, display-cost-button (Display Usage and Cost), stop-button, delete-button.
  6. Do
    control-openhands browser press Escape
  7. Note
    closes it (control-openhands browser count 'testid=conversation-name-context-menu' is 0) and focus is back on the trigger (control-openhands browser eval "document.activeElement.dataset.testid" is ellipsis-button).
  8. Note
    Open it again:
  9. Do
    control-openhands browser mouse-click 900 500
  10. Note
    closes it too (count 0).
Conversation menu offers Rename, Skills, Hooks, Agent tools, Export, Download Data, Display Usage and Cost, Stop Conversation and Delete Conversation.
The conversation menu groups transcript, agent configuration, runtime and deletion actions. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Conversation menu offers Rename, Skills, Hooks, Agent tools, Export, Download Data, Display Usage and Cost, Stop Conversation and Delete Conversation.

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. Doctor passed and pageErrors were zero. Error history contains GET /api/llm/balance 404 responses on this local backend; this is not a clean-errors claim.

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 fixture git-repo
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands conversation start --workspace qa-repo --prompt 'Reply with a markdown table with columns Step and Result and exactly two rows: Setup | ready, Reply | hello. Then a python code block containing print(1). Do not run any tools.' --wait --timeout 180
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands conversation events <conversation-id> --kinds ActionEvent --last 10
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser snapshot testid=agent-message
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser type testid=chat-input /
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser snapshot testid=slash-command-menu --max-lines 50
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser press Escape
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser fill testid=chat-input ''
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click 'testid=chat-pane-header >> testid=ellipsis-button'
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser snapshot testid=conversation-name-context-menu
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser screenshot testid=conversation-name-context-menu --feature F07.menu-open --name conversation-menu

Stop with confirmation #

  1. Note
    On /conversations/<running-id> run
  2. Do
    control-openhands browser click 'testid=chat-pane-header >> testid=ellipsis-button'
  3. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=stop-button'
  4. Check
    control-openhands browser snapshot 'role=dialog'

    (Confirm Stop Conversation, buttons Cancel and Confirm Close).

  5. Do
    control-openhands browser click 'role=dialog >> role=button[name="Cancel"]'
  6. Note
    closes it and
  7. Check
    control-openhands conversation status <running-id>
  8. Note
    stays running.
  9. Note
    Repeat the two clicks, then
  10. Do
    control-openhands browser click 'role=dialog >> role=button[name="Confirm Close"]' --expect-url '/conversations$'
  11. Note
    the toasts read Conversation stopped and conversation status <running-id> is paused.

Branch from a message #

  1. Note
    On /conversations/<id> (control-openhands browser goto /conversations/<id>; Stop with confirmation ended on /conversations) run
  2. Do
    control-openhands browser hover 'testid=agent-message'
  3. Do
    control-openhands browser click 'testid=agent-message >> role=button[name="Branch from here"]' --expect-url '/conversations/(?!<id>)[0-9a-f-]+'
  4. Check
    control-openhands browser text 'testid=conversation-name-title'

    (QA_F07 renamed (branch)) and

  5. Check
    control-openhands browser count 'testid=chat-interface >> testid=user-message'

    (1, the history is copied up to that reply).

  6. Note
    Back on /conversations/<id> (control-openhands browser goto /conversations/<id>),
  7. Do
    control-openhands browser hover 'testid=user-message'
  8. Do
    control-openhands browser click 'testid=user-message >> role=button[name="Branch from here"]' --expect-url '/conversations/(?!<id>)[0-9a-f-]+'

    (it prints /conversations/<branch-id>): the new branch has 0 user messages and

  9. Check
    control-openhands browser text 'testid=chat-input'

    reads the original prompt Reply with the single word: pong, still there a few seconds later and not sent (control-openhands conversation events <branch-id> --kinds MessageEvent has count 0).

Delete with confirmation #

  1. Note
    On a disposable conversation (for example the branch from F07.branch-from-message) run
  2. Do
    control-openhands browser click 'testid=chat-pane-header >> testid=ellipsis-button'
  3. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=delete-button'
  4. Check
    control-openhands browser snapshot 'role=dialog'
  5. Note
    : Confirm Delete Are you sure you want to delete the "<title>" conversation? This action cannot be undone. with Cancel and Confirm Delete.
  6. Do
    control-openhands browser click 'role=dialog >> role=button[name="Cancel"]'
  7. Note
    keeps the URL.
  8. Note
    Repeat the two clicks, then
  9. Do
    control-openhands browser click 'role=dialog >> role=button[name="Confirm Delete"]' --expect-url '/conversations$'
  10. Check
    control-openhands api GET '/api/conversations?ids=<id>'

    returns [null] and browser goto /conversations/<id> redirects as in F07.missing-redirect.

Paused conversation #

  1. Do
    control-openhands browser goto /conversations/<running-id>
  2. Note
    open the "..." menu and
  3. Check
    control-openhands browser testids 'testid=conversation-name-context-menu'
  4. Note
    : show-hooks-button and stop-button are absent.
  5. Note
    Close it with
  6. Do
    control-openhands browser mouse-click 900 500
  7. Check
    control-openhands browser click 'testid=server-status-menu-trigger'
  8. Check
    control-openhands browser text 'testid=server-status-context-menu'

    (Server Stopped / Start Runtime), then

  9. Check
    control-openhands browser click 'testid=server-status-context-menu >> testid=start-server-button'
  10. Wait
    control-openhands conversation wait <running-id> --until running,finished,idle --timeout 60
  11. Expect
    The status is running again and the menu reads Running / Stop Runtime.

Hooks #

  1. Note
    On /conversations/<running-id> (no workspace attached; Paused conversation left the browser there) open the "..." menu,
  2. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=show-hooks-button'
  3. Check
    control-openhands browser text 'testid=hooks-modal'
  4. Expect
    It reads Available Hooks, the workspace explanation and No hooks configured for this conversation. (the backend workspace root has no hooks.json).
  5. Check
    control-openhands browser network --clear
  6. Do
    control-openhands browser click 'testid=hooks-modal >> testid=refresh-hooks'
  7. Note
    : the text stays and
  8. Check
    control-openhands browser network

    shows POST /api/hooks.

  9. Do
    control-openhands browser click 'testid=close-hooks-modal'
  10. Note
    closes it.

Skills #

  1. Note
    Open the menu,
  2. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=show-skills-button'
  3. Check
    control-openhands browser snapshot 'role=dialog'

    (Public skills 12 and one button per skill),

  4. Do
    control-openhands browser click 'testid=skills-modal >> role=button[name="github Knowledge"]'
  5. Note
    the row expands to Triggers and Content.
  6. Do
    control-openhands browser press Escape
  7. Note
    closes it (browser count 'testid=skills-modal' is 0).

Project skills and configured hooks #

  1. Note
    Arrange (outside the UI) a project skill and a hook in the git fixture: mkdir -p <run>/workspace/qa-f07-repo/.agents/skills/qa-f07-skill <run>/workspace/qa-f07-repo/.openhands, write <run>/workspace/qa-f07-repo/.agents/skills/qa-f07-skill/SKILL.md with front matter name: qa-f07-skill, description: QA fixture skill for F07., triggers: [qaf07trigger] and the body QA_F07 skill body., and write <run>/workspace/qa-f07-repo/.openhands/hooks.json as {"pre_tool_use":[{"matcher":"terminal","hooks":[{"type":"command","command":"true","timeout":10}]}]}.
  2. Wait
    control-openhands conversation start --workspace qa-f07-repo --prompt "Reply with the single word: hi" --wait --timeout 240
  3. Note
    prints <hooks-id>;
  4. Check
    control-openhands api GET /api/conversations/<hooks-id>

    shows that hook_config.pre_tool_use holds the terminal matcher.

  5. Note
    On /conversations/<hooks-id> open the menu,
  6. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=show-skills-button'
  7. Check
    control-openhands browser snapshot 'role=dialog'
  8. Note
    : Project skills 1 with button "qa-f07-skill AgentSkills" above Public skills 12.
  9. Do
    control-openhands browser click 'testid=skills-modal >> role=button[name="qa-f07-skill AgentSkills"]'
  10. Note
    expands it to Triggers qaf07trigger Content QA_F07 skill body.;
  11. Do
    control-openhands browser click 'testid=skills-modal >> testid=refresh-skills'
  12. Note
    re-reads the workspace (browser network shows POST /api/skills).
  13. Note
    Close with
  14. Do
    control-openhands browser press Escape
  15. Note
    Hooks: open the menu,
  16. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=show-hooks-button'
  17. Check
    control-openhands browser snapshot 'testid=hooks-modal'
  18. Note
    : one collapsed event, button "Pre Tool Use 1 hook".
  19. Do
    control-openhands browser click 'testid=hooks-modal >> role=button[name="Pre Tool Use 1 hook"]'
  20. Note
    expands it to Matcher terminal Commands true Type: command Timeout: 10s (control-openhands browser screenshot --feature F07.hooks-modal --name workspace-hooks).
  21. Note
    Close it with
  22. Do
    control-openhands browser click 'testid=close-hooks-modal'
  23. Note
    and write <run>/workspace/qa-f07-repo/.openhands/hooks.json as {"pre_tool_use":[{"matcher":"terminal","hooks":[{"type":"command","command":"true","timeout":10}]}],"stop":[{"hooks":[{"type":"command","command":"echo qa-f07-stop"}]}]}.
  24. Expect
    A reopen keeps the list it read: run
  25. Check
    control-openhands browser network --clear
  26. Note
    open the menu,
  27. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=show-hooks-button'
  28. Note
    again, then
  29. Check
    control-openhands browser network --filter /api/hooks

    (no request) and

  30. Check
    control-openhands browser snapshot 'testid=hooks-modal'

    (still only button "Pre Tool Use 1 hook").

  31. Note
    Refresh re-reads the file:
  32. Do
    control-openhands browser click 'testid=hooks-modal >> testid=refresh-hooks'
  33. Check
    control-openhands browser network --filter /api/hooks

    shows POST /api/hooks and the snapshot adds button "Stop 1 hook";

  34. Do
    control-openhands browser click 'testid=hooks-modal >> role=button[name="Stop 1 hook"]'
  35. Note
    expands it to Matcher * Commands echo qa-f07-stop Type: command Timeout: 60s (the SDK defaults).
  36. Note
    Close with
  37. Do
    control-openhands browser click 'testid=close-hooks-modal'

Agent tools #

  1. Note
    Open the menu,
  2. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=show-agent-tools-button'
  3. Note
    the dialog testid=system-message-modal has tabs System Message (selected, the system prompt) and Available Tools.
  4. Do
    control-openhands browser click 'role=dialog >> role=tab[name="Available Tools"]'
  5. Check
    control-openhands browser count 'testid=system-message-modal >> testid=toggle-button'

    (9),

  6. Do
    control-openhands browser click 'testid=system-message-modal >> testid=toggle-button >> nth=0'
  7. Note
    expands terminal with its description.
  8. Note
    Close with
  9. Do
    control-openhands browser click 'testid=close-system-message-modal'

Export transcript #

  1. Note
    Open the menu,
  2. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=export-transcript-button'
  3. Check
    control-openhands browser snapshot 'role=dialog'

    shows Export conversation, radio Markdown document (.md) checked, Standalone web page (.html), and checkboxes Include tool call details and Include timestamps checked.

  4. Do
    control-openhands browser click 'testid=confirm-transcript-export'
  5. Check
    control-openhands browser downloads --last 1 --inspect
  6. Note
    : a <ms>-conversation-<id>.md (the harness prefixes a timestamp to the suggested conversation-<id>.md) lands in <run>/private/downloads starting # QA_F07 renamed, **Model:** deepseek/deepseek-flash, ## User with <sub> timestamps.
  7. Note
    Reopen,
  8. Do
    control-openhands browser click 'role=dialog >> role=radio[name="Standalone web page (.html)"]'
  9. Do
    control-openhands browser click 'role=dialog >> role=checkbox[name="Include timestamps"]'
  10. Note
    export: the .html has <title>QA_F07 renamed</title> and no timestamps.
  11. Note
    Reopen once more:
  12. Do
    control-openhands browser click 'testid=cancel-transcript-export'
  13. Note
    closes it without a download (the file count in <run>/private/downloads is unchanged); browser press Escape and testid=close-transcript-export-modal close it too.

Download data #

  1. Note
    Open the menu,
  2. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=download-trajectory-button'
  3. Check
    control-openhands browser downloads
  4. Expect
    A <ms>-conversation_<id>.zip lands in <run>/private/downloads; browser downloads --last 1 --inspect lists <id-without-dashes>/base_state.json and bash_events/, and every LLM api_key in base_state.json is masked as asterisks (**********).

Usage #

  1. Note
    Open the menu,
  2. Do
    control-openhands browser click 'testid=conversation-name-context-menu >> testid=display-cost-button'
  3. Wait
    control-openhands browser wait 'testid=usage-panel'
  4. Check
    control-openhands browser attr 'testid=right-panel-toggle' aria-pressed

    (true).

  5. Expect
    The panel shows Context Window, Token Usage and Total Cost (contents are F27).
Usage panel shows the context window percentage, input and output token totals, cache counts and total cost.
This completed no-tools response used 15,124 total tokens and cost $0.0017 in the panel. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Usage panel shows the context window percentage, input and output token totals, cache counts and total cost.

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. Doctor passed and pageErrors were zero. Error history contains GET /api/llm/balance 404 responses on this local backend; this is not a clean-errors claim.

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 fixture git-repo
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands conversation start --workspace qa-repo --prompt 'Reply with a markdown table with columns Step and Result and exactly two rows: Setup | ready, Reply | hello. Then a python code block containing print(1). Do not run any tools.' --wait --timeout 180
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands conversation events <conversation-id> --kinds ActionEvent --last 10
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser snapshot testid=agent-message
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser type testid=chat-input /
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser snapshot testid=slash-command-menu --max-lines 50
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser press Escape
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser fill testid=chat-input ''
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click 'testid=chat-pane-header >> testid=ellipsis-button'
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser snapshot testid=conversation-name-context-menu
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click 'testid=conversation-name-context-menu >> testid=display-cost-button'
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait testid=usage-panel
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click testid=conversation-tab-files
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait testid=files-tab
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click testid=file-tree-dir-src
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click testid=file-tree-file-src/calc.py
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser snapshot testid=files-tab
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser testids testid=files-tab
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser eval '(() => { let n=document.querySelector('"'"'[data-testid=files-tab-tree]'"'"'); const a=[]; for(let i=0;n&&i<4;i++,n=n.parentElement)a.push({tag:n.tagName,testid:n.dataset.testid,children:Array.from(n.children).map(c=>({tag:c.tagName,testid:c.dataset.testid}))}); return a; })()'
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click testid=conversation-tab-usage
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser hover testid=conversation-tab-usage
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser screenshot testid=usage-panel --feature F07.display-cost --name usage-panel

Right panel #

  1. Do
    control-openhands browser click 'testid=right-panel-toggle'
  2. Check
    control-openhands browser attr 'testid=right-panel-toggle' aria-label
  3. Note
    : it alternates Show panel / Hide panel, and
  4. Wait
    control-openhands browser wait 'testid=tabs-pane-header' --state hidden --timeout 5000

    (after Show panel) or --state visible (after Hide panel) follows; a plain browser visible right after the click still reads the old value while the panel animates.

  5. Note
    With the panel open,
  6. Do
    control-openhands browser reload
  7. Note
    and browser attr 'testid=right-panel-toggle' aria-pressed reads false (closed by design; the selected tab is remembered).

Overview #

  1. Do
    control-openhands browser click 'testid=conversation-overview-toggle'
  2. Check
    control-openhands browser visible 'testid=conversation-overview-panel'

    (true) and browser attr 'testid=conversation-overview-toggle' aria-label (Hide overview).

  3. Do
    control-openhands browser click 'testid=right-panel-toggle'
  4. Wait
    control-openhands browser wait 'testid=conversation-overview-column' --state hidden --timeout 5000
  5. Note
    succeeds (an immediate browser visible can still read true).
  6. Note
    With the panel open,
  7. Do
    control-openhands browser hover 'testid=conversation-overview-toggle'
  8. Wait
    control-openhands browser wait 'testid=conversation-overview-peek' --timeout 5000

    shows the peek.

  9. Note
    Clicking the overview toggle now closes the panel (aria-pressed false) and shows the overview (browser wait 'testid=conversation-overview-column' --state visible).
  10. Note
    Click the overview toggle once more to restore the closed state.

Git actions #

  1. Do
    control-openhands browser click 'testid=conversation-git-actions-toggle'
  2. Check
    control-openhands browser testids 'testid=conversation-git-actions-menu'

    (-commit, -pull, -push, -create-pr, -create-new-branch, labels Commits, Pull, Push, Create PR, Create New Branch).

  3. Do
    control-openhands browser click 'testid=conversation-git-actions-menu >> testid=conversation-git-actions-commit'
  4. Check
    control-openhands browser text 'testid=chat-input'
  5. Note
    : Please review the current changes and create a git commit with a concise, descriptive message.; conversation events <id> --kinds MessageEvent shows no new message.
  6. Note
    Each item replaces the composer text: -pull gives Please pull the latest code from the repository., -create-new-branch gives Please create a new branch with a descriptive name related to the work you plan to do., and the Push and Create PR prompts name GitHub by default.
  7. Do
    control-openhands browser press Escape
  8. Note
    closes the open menu.
  9. Note
    Clear the composer with
  10. Do
    control-openhands browser fill 'testid=chat-input' ''

Panel route #

  1. Do
    control-openhands browser viewport phone
  2. Note
    open /conversations/<id> (control-openhands browser goto /conversations/<id>),
  3. Check
    control-openhands browser click 'testid=right-panel-toggle' --expect-url '/panel$'
  4. Check
    control-openhands browser testids --filter mobile

    (conversation-mobile-panel-top, conversation-mobile-panel-back), then

  5. Check
    control-openhands browser click 'testid=conversation-mobile-panel-back' --expect-url '/conversations/[0-9a-f-]+$'
  6. Note
    Direct entry at phone width:
  7. Do
    control-openhands browser goto /conversations/<id>/panel
  8. Wait
    control-openhands browser wait 'testid=conversation-mobile-panel-back'
  9. Note
    succeed (control-openhands browser screenshot --feature F07.panel-route --name phone-direct).
  10. Expect
    The panel page serves every width up to 1024 px: after
  11. Do
    control-openhands browser viewport tablet
  12. Note
    the URL still ends /panel and
  13. Check
    control-openhands browser count 'testid=conversation-mobile-panel-back'

    is 1.

  14. Note
    Wider, there is no panel page: run
  15. Do
    control-openhands browser viewport desktop
  16. Wait
    control-openhands browser wait-url '/conversations/[0-9a-f-]+$'

    (the app replaces /panel with /conversations/<id>),

  17. Check
    control-openhands browser count 'testid=conversation-mobile-panel-back'

    (0) and

  18. Check
    control-openhands browser attr 'testid=right-panel-toggle' aria-pressed

    (true: the drawer is open beside the chat).

  19. Expect
    A desktop-width visit lands the same way (#18045):
  20. Do
    control-openhands browser goto /conversations/<id>/panel
  21. Wait
    control-openhands browser wait-url '/conversations/[0-9a-f-]+$'
  22. Note
    the same count (0) and aria-pressed (true);
  23. Do
    control-openhands browser screenshot --feature F07.panel-route --name desktop-redirect

    shows the chat with the drawer open beside it.

Git control bar #

  1. Note
    On /conversations/<git-id> (control-openhands browser goto /conversations/<git-id>; Panel route ends on /conversations/<id>) run
  2. Check
    control-openhands browser snapshot 'testid=interactive-chat-box'
  3. Note
    : button "qa-f07-repo" [disabled] and the branch text main, no Pull/Push.
  4. Note
    Add a GitHub origin to the fixture (arrange step, outside the UI):
  5. Do
    git -C <run>/workspace/qa-f07-repo remote add origin https://github.com/qa-example/qa-f07-repo.git
  6. Do
    control-openhands browser reload
  7. Note
    and the snapshot again: link qa-example/qa-f07-repo (https://github.com/qa-example/qa-f07-repo), link main (…/tree/main) and buttons Pull, Push, Pull Request.

Pull sends at once #

  1. Do
    control-openhands browser click 'testid=interactive-chat-box >> role=button[name="Pull"][exact]'
  2. Wait
    control-openhands browser wait 'testid=user-message >> has-text=Please pull the latest code' --timeout 15000
  3. Note
    conversation status <git-id> is running.
  4. Note
    Stop it right away with Stop Conversation as in F07.stop-confirm (the fake remote cannot be pulled).
  5. Note
    Push and Pull Request are blocked: they need a real remote and GitHub credentials.

Cloud-only #

  1. Note
    Blocked without a Cloud backend.
  2. Note
    On local, confirm the absence: the menu testids (see F07.menu-open) have no share-publicly-button, and the git control bar shows no Connect Repo chip.
  3. Note
    With Cloud: the menu's Share publicly switch (share-publicly-button, then copy-share-link-button, open-share-link-button), the repo chip Connect Repo opening the Open repository modal (close-open-repository-modal), /conversations/task-<uuid> while a conversation starts, and the Unavailable for Archives tooltips (see F25).

Phone #

  1. Do
    control-openhands browser viewport phone
  2. Check
    control-openhands browser testids 'testid=chat-pane-header'

    (adds sidebar-mobile-menu-toggle; all header controls visible), open each info dialog from the menu and

  3. Check
    control-openhands browser bbox 'testid=skills-modal'

    (likewise hooks-modal, system-message-modal): insideViewport is true and pageHorizontalOverflow false.

  4. Do
    control-openhands browser click 'testid=chat-pane-header >> testid=ellipsis-button'
  5. Check
    control-openhands browser bbox 'testid=conversation-name-context-menu'
  6. Note
    : inside the viewport too (x about 145, width about 237), and
  7. Do
    control-openhands browser screenshot --feature F07.phone --name header-menu

    shows every label whole (Show Available Skills, Download conversation data, Delete Conversation).

  8. Do
    control-openhands browser press Escape
  9. Note
    closes it.
  10. Note
    Return with
  11. Do
    control-openhands browser viewport desktop

Gotchas and known limits

  • The stop and delete confirmation buttons have no test ids at runtime (BrandButton takes testId, the modals pass data-testid) (#17915): select them by role inside role=dialog.
  • 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's Automations section has no opener in the UI (the overview menu offers Workspace and Git sections only).
  • 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.
  • Expected: an agent hook shows its system_prompt, the text it runs with, even when it also carries a prompt. Known failure (reproduced 2026-10-09): with {"session_start":[{"hooks":[{"type":"agent","prompt":"QA_F07 stray prompt","system_prompt":"QA_F07 agent system prompt"}]}]} in hooks.json, close, reopen and Refresh the dialog, then expand Session Start 1 hook: it reads Matcher * Commands QA_F07 stray prompt Type: agent Timeout: 60s, and QA_F07 agent system prompt appears nowhere (control-openhands browser count 'text=QA_F07 agent system prompt' is 0), although control-openhands api POST /api/hooks --write --data '{"project_dir":"<run>/workspace/qa-f07-repo"}' returns both fields (#18206). Write the recipe's two-event hooks.json (pre_tool_use and stop) back and Refresh afterwards.
  • Expected: with the Agent Server down, Refresh in the Hooks dialog shows a fetch error about hooks. Known failure (reproduced 2026-10-06): after control-openhands service stop agent-server on an open dialog, refresh-hooks and browser wait 'testid=hooks-modal >> text=Failed to fetch' --timeout 30000, it reads Failed to fetch skills. Please try again later. (the shared COMMON$FETCH_ERROR string) (#18088). 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; repro candidate, not filed yet, see F05 Gotchas). Enter in the field sends it.

Source paths: src/routes/conversation.tsx, src/components/features/conversation/, src/components/features/conversation-panel/, src/components/features/controls/server-status-context-menu.tsx, src/hooks/use-conversation-name-context-menu.ts.