Application settings
F16 · 20 mapped behaviors in the Self-Healing Codebase Project. Follow the prerequisites, actions and expected results; expand the original CLI recipe to replay it.
778 behavior IDs · 28 families · 844 named recipes & support steps
16 October 2026 · PR #18334 at 9a3c3a69
Canvas 1.27.1 · Agent Server 1.55.0 · automation 1.20.0
This is the map at the revision above, including its open bug notes. It does not incorporate later changes on main or claim a fresh verification run. Setup & reading guide · Download the scenario inventory
All scenarios & marker key · Open a script to read Given / Do / Expect. Expand the exact recipe for replayable commands.
No scenarios match. Try a different word or reset.
F16 Application settings
20 behaviors · 24 scripts & contracts · Maintained source
Entry points & prerequisites
What this family covers
Settings → Application holds the user's app-wide preferences: UI language, colour theme, anonymous usage data, sound notifications, the sidebar Getting Started checklist, which LLM profile writes conversation titles, an optional voice-input transcription endpoint, and the git identity used for commits. The page mixes two save models: language, analytics, sound, title model and git identity wait for Save Changes and are stored on the backend; theme, checklist and voice input apply instantly and live in this browser only.
How a user gets here
- Sidebar settings gear (
backend-selector-settings-link), then Application in the settings navigation (sidebar-settings-/settings/app). The gear lands on/settings/agents, not on this page. - Direct URL
/settings/app(also the default settings page for locked-to-Cloud deployments). - Command menu (
Control+korMeta+k, orcommand-menu-triggerin the sidebar): search "Application", choose Application settings ("Change app language, theme, and preferences"). - Manage LLM profiles link under Conversation titles leaves this page for
/settings/llm(F16.manage-profiles-link); the LLM settings row menu is how a user deletes a profile (owned by the LLM profiles family).
Given
Preconditions:
- Baseline state (launched, doctored,
onboard --skipdone; it stores analytics off, so the analytics switch starts unchecked). F16.title-model,F16.title-model-fallbackand the agent half ofF16.git-identityneed LLM profiles:control-openhands llm preset deepseek --api-key-file <key file>(or--api-key-env DEEPSEEK_API_KEY), which savesdeepseek-flash(active) anddeepseek-pro.- Read the starting values once:
control-openhands api GET /api/settings; backend-saved fields are underbody.misc_settings.app_preferences(language,user_consents_to_analytics,enable_sound_notifications,title_llm_profile,git_user_name,git_user_email). Keys absent there mean defaults. - The three switches are visually hidden native checkboxes inside a
<label>: click the label text scoped to the form or focus them with Tab and toggle with Space, and read state withcontrol-openhands browser eval "document.querySelector('[data-testid=enable-analytics-switch]').checked"(same forenable-sound-notifications-switch,show-getting-started-checklist-switch). - Every bullet starts on
/settings/appat the desktop viewport unless it says otherwise (control-openhands browser goto /settings/app, thencontrol-openhands browser wait 'testid=submit-button'). F16.analytics-cloudis blocked without an OpenHands Cloud backend and account.
LanguageF16.languageMapped behavior
Scenario F16--language · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “Language”.
- DoChoose “Français”.
- DoChoose “Submit”.
- DoReload the page.
- DoChoose “Language”.
- DoChoose “English”.
- DoChoose “Submit”.
- DoReload the page.
Expect
choosing a language and saving switches the whole UI at once and persists after a reload; <html lang> and <html dir> follow the chosen language (rtl for Arabic).
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=language-input' (15 options), control-openhands browser click 'role=option[name="Français"]', control-openhands browser enabled 'testid=submit-button' (true), control-openhands browser click 'testid=submit-button', then control-openhands browser text 'testid=submit-button'. It reads Enregistrer les modifications without a reload. Run control-openhands browser reload, control-openhands browser value 'testid=language-input' (Français), control-openhands browser screenshot --feature F16.language --name french (the screenshot, not a scoped snapshot, shows the sidebar's "Nouvelle discussion" and the headings "Titres des conversations", "Saisie vocale") and control-openhands api GET /api/settings (language is fr). Then check the document language with control-openhands browser eval "document.documentElement.lang + ' ' + document.documentElement.dir" (fr ltr) and the translated sidebar trigger with control-openhands browser attr 'testid=command-menu-trigger' aria-label (Rechercher des commandes). Choose role=option[name="Arabic"] the same way (click the input, the option, Save, reload): the eval reads ar rtl, and control-openhands browser bbox 'aside[data-collapsed]' has x 1140, so the 300 px sidebar now sits at the right edge of the 1440 px viewport. control-openhands browser screenshot --feature F16.language --name arabic shows the whole layout flipped right to left: the sidebar on the right, the settings navigation to its left, and the form on the left with its Arabic labels and values aligned right. Restore: control-openhands browser click 'testid=language-input', control-openhands browser click 'role=option[name="English"]', control-openhands browser click 'testid=submit-button', control-openhands browser reload, control-openhands browser text 'testid=submit-button' reads Save Changes and the eval reads en ltr.
Colour themeF16.color-themeMapped behavior
Scenario F16--colour-theme · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “Color theme”.
- DoChoose “Light+”.
- DoReload the page.
- DoOpen /.
- DoOpen /settings/app.
Expect
choosing a colour theme applies it immediately without Save, persists after a reload and on other pages, and is never sent to the backend.
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=color-theme-input', control-openhands browser snapshot 'role=listbox' (OpenHands-DeepSea, OpenHands-Neutral [selected], OpenHands-Neo, Light+, Solarized Light), control-openhands browser click 'role=option[name="Light+"]', then control-openhands browser eval "document.documentElement.style.colorScheme + ' ' + localStorage.getItem('openhands-color-theme')" (light light-plus) and control-openhands browser enabled 'testid=submit-button' (false: no Save needed). control-openhands browser screenshot --feature F16.color-theme --name light-plus shows a white page. Second views: control-openhands browser reload then control-openhands browser value 'testid=color-theme-input' (Light+); control-openhands browser goto / then control-openhands browser eval "getComputedStyle(document.body).backgroundColor + ' ' + document.documentElement.style.colorScheme" (rgb(255, 255, 255) light); control-openhands api GET /api/settings contains no theme key. Restore on /settings/app (control-openhands browser goto /settings/app; the second view left /) by choosing role=option[name="OpenHands-Neutral"] the same way (dark openhands-neutral).
Analytics consentF16.analyticsMapped behavior
Scenario F16--analytics-consent · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “App settings screen” / “Send anonymous usage data”.
- DoChoose “Submit”.
- WaitWait for the element identified in the CLI recipe to appear.
- DoReload the page.
Expect
the "Send anonymous usage data" switch saves with the form and sets browser telemetry consent to granted/denied.
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=app-settings-screen >> text=Send anonymous usage data', read the switch with the eval from Preconditions (true), control-openhands browser click 'testid=submit-button', control-openhands browser wait-text 'Settings saved', control-openhands browser reload, read the switch again (true), control-openhands api GET /api/settings (user_consents_to_analytics: true) and control-openhands browser eval "localStorage.getItem('openhands-telemetry-consent')" (granted). Restore by clicking the label again and saving; consent reads denied and the API false.
Cloud forces analyticsF16.analytics-cloudBlocked
Scenario F16--cloud-forces-analytics · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoBlocked: needs a Cloud backend selected in the backend selector. Expected:
control-openhands browser enabled 'testid=enable-analytics-switch'isfalseand the switch reads checked.
Expect
on a Cloud backend the analytics switch is forced on and disabled.
Exact CLI recipe & bug checks
Blocked: needs a Cloud backend selected in the backend selector. Expected: control-openhands browser enabled 'testid=enable-analytics-switch' is false and the switch reads checked.
Consent reversal with an in-flight Automation writeF16.analyticsMapped behavior
Scenario F16--consent-reversal-with-an-in-flight-automation-write · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- SetupChange the run-owned service state: pause automation.
- SetupChange the run-owned service state: resume automation.
Expect
the "Send anonymous usage data" switch saves with the form and sets browser telemetry consent to granted/denied.
Exact CLI recipe & bug checks
With a non-DNT build using a dummy PostHog key and loopback-only VITE_POSTHOG_HOST (no external analytics delivery), save opt-in and confirm its consent POST returned 200. Run service pause automation, save opt-out then opt-in through the same switch, and service resume automation before the five-second request timeout. Record the UI transitions and read browser network --filter 'telemetry/consent' --bodies; the final granted value must be posted and acknowledged. Resume before stop. The default hard-DNT build cannot exercise this reversal.
Sound notificationsF16.soundMapped behavior
Scenario F16--sound-notifications · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “App settings screen” / “Sound Notifications”.
- DoChoose “Submit”.
- WaitWait for the element identified in the CLI recipe to appear.
- DoReload the page.
- DoStart a conversation with the prompt “Reply with only the word ok. Do not run any tools.”.
- DoOpen /settings/app.
- DoStart a conversation as specified in the recipe.
Expect
the "Sound Notifications" switch saves with the form; with it on, a sound plays when the agent finishes or needs input.
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=app-settings-screen >> text=Sound Notifications', control-openhands browser click 'testid=submit-button', control-openhands browser wait-text 'Settings saved', control-openhands browser reload, read enable-sound-notifications-switch (true) and control-openhands api GET /api/settings (enable_sound_notifications: true). Restore by toggling and saving again (false). Audible half: with the switch on and saved, control-openhands conversation start --prompt "Reply with only the word ok. Do not run any tools." --wait --timeout 180, then control-openhands browser media: plays lists one /assets/notification-<hash>.mp3 at the moment the conversation finished. With the switch off, the same check after a conversation reads plays: [] (the F16.git-identity conversation below doubles as that negative check). Turn the switch off and save afterwards (control-openhands browser goto /settings/app first: conversation start left the browser on the conversation).
Getting Started checklistF16.checklistMapped behavior
Scenario F16--getting-started-checklist · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “App settings screen” / “Show Getting Started checklist”.
- DoReload the page.
Expect
the "Show Getting Started checklist" switch hides or shows the sidebar checklist immediately, without Save, and survives a reload.
Exact CLI recipe & bug checks
Run control-openhands browser count 'testid=sidebar-onboarding-checklist' (1), control-openhands browser click 'testid=app-settings-screen >> text=Show Getting Started checklist', then the same count (0, immediately) and control-openhands browser enabled 'testid=submit-button' (false). control-openhands browser screenshot --feature F16.checklist --name hidden shows the sidebar without the "Getting started" card. control-openhands browser reload keeps the count at 0 and the switch unchecked. Click the label again to restore; the count returns to 1.
Title generation modelF16.title-modelMapped behavior
Scenario F16--title-generation-model · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “Title llm profile”.
- DoChoose the option / containing “deepseek-pro”.
- DoChoose “Submit”.
- WaitWait for the element identified in the CLI recipe to appear.
- DoReload the page.
- DoStart a conversation with the prompt “Reply with only the word ok. Do not run any tools.”.
- DoOpen /settings/app.
Expect
the Title generation model dropdown offers "Automatic (recommended)" plus one name · model option per LLM profile; the choice persists and new conversations still get an automatic title.
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=title-llm-profile-input' and control-openhands browser snapshot 'role=listbox': options Automatic (recommended), deepseek-flash · deepseek/deepseek-flash, deepseek-pro · deepseek/deepseek-v4-pro. Run control-openhands browser click 'role=option >> has-text=deepseek-pro', control-openhands browser click 'testid=submit-button', control-openhands browser wait-text 'Settings saved', control-openhands browser reload, control-openhands browser value 'testid=title-llm-profile-input' (deepseek-pro · deepseek/deepseek-v4-pro) and control-openhands api GET /api/settings (title_llm_profile: "deepseek-pro"). Then control-openhands conversation start --prompt "Reply with only the word ok. Do not run any tools." --wait --timeout 180 and, a few seconds later, control-openhands conversation status <id> with the id it printed: title is a generated title: an emoji and a short phrase whose wording varies (for example 🔧 Acknowledge with ok only). The start output's own title may already be set; when it is still null, the title is written a few seconds after the run finishes. Return with control-openhands browser goto /settings/app, choose role=option[name="Automatic (recommended)"], save; title_llm_profile is then absent from app_preferences.
Manage LLM profiles linkF16.manage-profiles-linkMapped behavior
Scenario F16--manage-llm-profiles-link · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “Manage LLM profiles”.
Expect
"Manage LLM profiles" goes to /settings/llm.
Exact CLI recipe & bug checks
Run control-openhands browser click 'role=link[name="Manage LLM profiles"]' then control-openhands browser url; it ends in /settings/llm and control-openhands browser count 'testid=profile-row' lists the profiles.
Deleted profile falls backF16.title-model-fallbackMapped behavior
Scenario F16--deleted-profile-falls-back · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- SetupConfigure or inspect the model profiles specified in the recipe.
- DoChoose “Title llm profile”.
- DoChoose the option / containing “qa-title”.
- DoChoose “Manage LLM profiles”.
- DoChoose “Profile row” / containing “qa-title” / “Profile menu trigger”.
- DoChoose “Profile actions menu” / “Profile delete”.
- DoChoose “Delete profile confirm”.
- DoChoose “App settings”.
- SetupArrange the prerequisite backend state through the API.
- DoReload the page.
Expect
if the chosen profile is deleted (from LLM settings, or outside the UI), the dropdown shows Automatic and the form stays clean.
Exact CLI recipe & bug checks
Arrange a throwaway profile: control-openhands llm set --profile qa-title --model deepseek/deepseek-flash --api-key-file <key file> --no-activate --no-validate. On /settings/app select it (control-openhands browser click 'testid=title-llm-profile-input', control-openhands browser click 'role=option >> has-text=qa-title', save). Delete it through the UI: control-openhands browser click 'role=link[name="Manage LLM profiles"]', control-openhands browser click 'testid=profile-row >> has-text=qa-title >> testid=profile-menu-trigger', control-openhands browser click 'testid=profile-actions-menu >> testid=profile-delete', control-openhands browser click 'testid=delete-profile-confirm', then control-openhands browser click 'testid=sidebar-settings-/settings/app'. control-openhands browser value 'testid=title-llm-profile-input' is Automatic (recommended), Save is disabled, and title_llm_profile is absent from the API's app_preferences. For a preference that goes stale outside the UI, arrange control-openhands api PATCH /api/settings --data '{"misc_settings_diff":{"app_preferences":{"title_llm_profile":"qa-missing"}}}' --write, then control-openhands browser reload: the dropdown still shows Automatic (recommended) and Save stays disabled, while the API keeps qa-missing until the next Save of this form. Any Save writes the form's current preferences, which no longer hold the stale key, so saving an unrelated change removes it: toggle Sound Notifications, save, toggle back, save.
Voice input endpointF16.voice-endpointMapped behavior
Scenario F16--voice-input-endpoint · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoEnter “http://127.0.0.1:9/v1” in “Transcription base url”.
- DoEnter “qa-dummy-key” in “Transcription api key”.
- DoEnter “qa-whisper” in “Transcription model”.
- DoReload the page.
- DoEnter “” in “Transcription base url”.
Expect
Voice input Base URL, API Key (masked) and Model save on every keystroke in this browser only, independently of Save; the key lives in session storage.
Exact CLI recipe & bug checks
Run control-openhands browser fill 'testid=transcription-base-url-input' http://127.0.0.1:9/v1, control-openhands browser fill 'testid=transcription-api-key-input' qa-dummy-key, control-openhands browser fill 'testid=transcription-model-input' qa-whisper, then control-openhands browser enabled 'testid=submit-button' (false) and control-openhands browser attr 'testid=transcription-api-key-input' type (password). After control-openhands browser reload, control-openhands browser value 'testid=transcription-model-input' is qa-whisper (same for the other two). control-openhands browser eval "localStorage.getItem('openhands-transcription-endpoint') + ' | session=' + sessionStorage.getItem('openhands-transcription-api-key')" reads {"baseUrl":"http://127.0.0.1:9/v1","model":"qa-whisper"} | session=qa-dummy-key; the API body has no qa-whisper. Clear each with control-openhands browser fill 'testid=transcription-base-url-input' '' (and the other two). Using the endpoint from the composer microphone is not driven here (needs microphone capture and a transcription server).
Git identityF16.git-identityKnown failure
Scenario F16--git-identity · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoEnter “QA Tester” in “Git user name”.
- DoEnter “qa-tester@example.com” in “Git user email”.
- DoChoose “Submit”.
- WaitWait for the element identified in the CLI recipe to appear.
- DoReload the page.
- DoStart a conversation with the prompt “Run exactly this command and reply with only its output: env | grep -c '^GIT_'; git init -q qa_t && cd qa_t && git commit --allow-empty -qm x; git log -1 --format='%an <%ae>'”.
Expect
Git Username and Git Email save with the form, persist after a reload, and are what the agent's commits use.
Known bug observations
Known failure:** the observation is 0 Author identity unknown ...: the identity is stored only in misc_settings.app_preferences and never reaches the agent.
Exact CLI recipe & bug checks
Run control-openhands browser fill 'testid=git-user-name-input' 'QA Tester', control-openhands browser fill 'testid=git-user-email-input' 'qa-tester@example.com', control-openhands browser click 'testid=submit-button', control-openhands browser wait-text 'Settings saved', control-openhands browser reload, then control-openhands browser value 'testid=git-user-name-input' (QA Tester) and control-openhands api GET /api/settings (git_user_name, git_user_email set). Agent half: control-openhands conversation start --prompt "Run exactly this command and reply with only its output: env | grep -c '^GIT_'; git init -q qa_t && cd qa_t && git commit --allow-empty -qm x; git log -1 --format='%an <%ae>'" --wait --timeout 240, then control-openhands conversation events <id> --kinds ObservationEvent,MessageEvent with the id from the start output. Expected: QA Tester <qa-tester@example.com>. Known failure: the observation is 0 Author identity unknown ...: the identity is stored only in misc_settings.app_preferences and never reaches the agent.
Git validation and defaultsF16.git-identity-validationMapped behavior
Scenario F16--git-validation-and-defaults · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoEnter “not-an-email” in “Git user email”.
- DoChoose “Submit”.
- DoEnter “” in “Git user email”.
- DoEnter “” in “Git user name”.
- WaitWait for the element identified in the CLI recipe to appear.
- DoReload the page.
Expect
an email without @ is refused by native validation; empty fields save the defaults openhands / openhands@all-hands.dev.
Exact CLI recipe & bug checks
Run control-openhands browser fill 'testid=git-user-email-input' 'not-an-email', control-openhands browser click 'testid=submit-button', then control-openhands browser eval "document.querySelector('[data-testid=git-user-email-input]').validationMessage" (Please include an '@' in the email address. ...); the API email is unchanged. Then control-openhands browser fill 'testid=git-user-email-input' '', control-openhands browser fill 'testid=git-user-name-input' '', click submit, control-openhands browser wait-text 'Settings saved', control-openhands browser reload: the inputs read openhands and openhands@all-hands.dev, which also restores the baseline.
Save button statesF16.save-statesKnown failure
Scenario F16--save-button-states · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- WaitWait for the element identified in the CLI recipe to appear.
- SetupChange the run-owned service state: stop agent-server.
- WaitWait for the element identified in the CLI recipe to appear.
- DoRestart the run-owned stack as specified.
- DoOpen /settings/app.
Expect
Save is enabled only while a saved field differs from the stored value; while a save is pending the whole form, Save included, is replaced by the app-settings-skeleton (so the "Saving..." label in the source never shows); success toasts "Settings saved", failure toasts the server's message (An error occurred when it has none) and should keep the user's edits for a retry.
Known bug observations
Known failure:** control-openhands browser eval "document.querySelector('[data-testid=enable-sound-notifications-switch]').checked" is false and Save is disabled: the edit is silently discarded. Bring the backend back with control-openhands restart, control-openhands doctor, control-openhands browser goto /settings/app.
Exact CLI recipe & bug checks
Click the analytics label once: control-openhands browser enabled 'testid=submit-button' is true; click it again: false (back to the stored value). Each successful save above made while the UI is in English shows the toast found by control-openhands browser wait-text 'Settings saved'; the Arabic save and the English restore in F16.language are made from a French and an Arabic UI and toast Paramètres enregistrés and تم حفظ الإعدادات instead (see Gotchas). Pending and failure: run control-openhands service stop agent-server (the open page keeps its form), click testid=app-settings-screen >> text=Sound Notifications, start a sampler with control-openhands browser eval "(() => { window.__seen = []; window.__iv = setInterval(() => { const s = document.querySelector('[data-testid=submit-button]'); const k = (s ? s.textContent : '-') + ' skeleton=' + !!document.querySelector('[data-testid=app-settings-skeleton]'); if (window.__seen.at(-1) !== k) window.__seen.push(k); }, 10); return 'started'; })()", click testid=submit-button, control-openhands browser wait-text 'An error occurred' (after three PATCH /api/settings attempts answer 502, browser network --filter /api/settings shows them; the ingress's plain-text 502 body carries no server message, so the toast is the generic one, #18051), then control-openhands browser eval "(() => { clearInterval(window.__iv); return window.__seen.join(' -> '); })()": it starts with Save Changes skeleton=false -> - skeleton=true, i.e. the form and its Save button are replaced by the skeleton while pending; Saving... never appears. By the time the failure toast is read the form has usually come back, adding -> Save Changes skeleton=false; assert the prefix. Expected after the failure: the Sound switch stays on so the user can retry. Known failure: control-openhands browser eval "document.querySelector('[data-testid=enable-sound-notifications-switch]').checked" is false and Save is disabled: the edit is silently discarded. Bring the backend back with control-openhands restart, control-openhands doctor, control-openhands browser goto /settings/app.
Keyboard and screen readersF16.keyboardMapped behavior
Scenario F16--keyboard-and-screen-readers · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoUse browser focus as specified in the exact recipe.
- DoPress Tab.
Expect
every control, including the three switches, is reachable with Tab and exposed to assistive technology.
Exact CLI recipe & bug checks
Run control-openhands browser focus 'testid=color-theme-input', then repeat control-openhands browser press Tab and control-openhands browser eval "(document.activeElement.getAttribute('data-testid')||'') + ' ' + document.activeElement.tagName". Expected (the acceptance criterion): focus visits the three switches before title-llm-profile-input. The first Tab lands on enable-analytics-switch, followed by enable-sound-notifications-switch and show-getting-started-checklist-switch; Space toggles the focused checkbox. control-openhands browser snapshot 'testid=app-settings-screen' --feature F16.keyboard --name aria exposes each checkbox label and checked state. The label draws a visible focus outline.
sh
control-openhands browser viewport desktop
control-openhands browser goto /settings/app
control-openhands browser wait 'testid=submit-button'
control-openhands browser record start --feature F16.keyboard --name keyboard-desktop
control-openhands browser focus 'testid=color-theme-input'
control-openhands browser press Tab
control-openhands browser press Escape
control-openhands browser screenshot --feature F16.keyboard --name keyboard-desktop
control-openhands browser record stop --gif
control-openhands assert --field value --equals enable-analytics-switch -- browser eval "document.activeElement.getAttribute('data-testid')"
sh
control-openhands browser viewport phone
control-openhands browser goto /settings/app
control-openhands browser wait 'testid=submit-button'
control-openhands browser record start --feature F16.keyboard --name keyboard-phone
control-openhands browser focus 'testid=color-theme-input'
control-openhands browser press Tab
control-openhands browser press Escape
control-openhands browser screenshot --feature F16.keyboard --name keyboard-phone
control-openhands browser record stop --gif
control-openhands assert --field value --equals enable-analytics-switch -- browser eval "document.activeElement.getAttribute('data-testid')"
Phone layoutF16.phoneMapped behavior
Scenario F16--phone-layout · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- SetupUse the phone viewport.
- DoOpen /settings/app.
- DoScroll “Submit” as specified in the recipe.
- SetupUse the desktop viewport.
Expect
the form fits a 390 px viewport without horizontal overflow and Save is reachable by scrolling.
Exact CLI recipe & bug checks
Run control-openhands browser viewport phone, control-openhands browser goto /settings/app, control-openhands browser bbox 'testid=app-settings-screen' (pageHorizontalOverflow: false, width 348 at x 16), control-openhands browser screenshot --feature F16.phone --name top, control-openhands browser scroll 'testid=submit-button', control-openhands browser bbox 'testid=submit-button' (insideViewport: true) and control-openhands browser screenshot --feature F16.phone --name bottom. Return with control-openhands browser viewport desktop.
Clear the title modelF16.title-model-clearMapped behavior
Scenario F16--clear-the-title-model · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “Title llm profile”.
- DoChoose the option / containing “deepseek-flash”.
- DoHover over “Title llm profile”.
- DoChoose “Title llm profile” / its containing control.
Expect
the Title generation model combobox has a clear (×) button, shown on hover; it resets the choice to Automatic (recommended).
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=title-llm-profile-input', control-openhands browser click 'role=option >> has-text=deepseek-flash' (Save enables), control-openhands browser hover 'testid=title-llm-profile-input', then control-openhands browser click 'testid=title-llm-profile-input >> xpath=../..//button[not(@aria-label)]'. control-openhands browser value 'testid=title-llm-profile-input' reads Automatic (recommended) and control-openhands browser enabled 'testid=submit-button' is false. Without the hover the click times out (<div ...> intercepts pointer events).
Filter a dropdown by typingF16.dropdown-filterMapped behavior
Scenario F16--filter-a-dropdown-by-typing · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoEnter “Deu” in “Language”.
- DoPress Escape.
Expect
typing into the Language (or any) combobox filters its options; Escape restores the stored value and leaves Save disabled.
Exact CLI recipe & bug checks
Run control-openhands browser fill 'testid=language-input' Deu and control-openhands browser snapshot 'role=listbox': the only option is Deutsch. Save is briefly true while the text is typed; control-openhands browser press Escape restores control-openhands browser value 'testid=language-input' to English and control-openhands browser enabled 'testid=submit-button' to false.
Browser vs backend scopeF16.browser-scopeMapped behavior
Scenario F16--browser-vs-backend-scope · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoChoose “Show getting started checklist switch” / its containing control.
- DoStart with a fresh browser profile.
- DoFollow the onboarding setup in the recipe.
- DoOpen /settings/app.
- WaitWait for “Submit” to appear.
- DoChoose “Language”.
- DoChoose “English”.
- DoChoose “Submit”.
- DoReload the page.
- WaitWait for “Submit” to appear.
Expect
backend-saved preferences (language and the rest of the Save group) follow the user to a fresh browser profile, while theme and checklist visibility return to their defaults there.
Exact CLI recipe & bug checks
Choose Deutsch in testid=language-input and save, choose Solarized Light in testid=color-theme-input, and hide the checklist with control-openhands browser click 'testid=show-getting-started-checklist-switch >> xpath=ancestor::label' (the label text is German now). Then control-openhands browser reset, control-openhands onboard --skip, control-openhands browser goto /settings/app, control-openhands browser wait 'testid=submit-button': control-openhands browser text 'testid=submit-button' is Änderungen speichern and control-openhands browser value 'testid=language-input' is Deutsch (backend), while control-openhands browser value 'testid=color-theme-input' is OpenHands-Neutral, control-openhands browser eval "localStorage.getItem('openhands-color-theme')" is null and control-openhands browser count 'testid=sidebar-onboarding-checklist' is 1 (browser-only, back to defaults). Restore English (the option keeps its English name in the German UI): control-openhands browser click 'testid=language-input', control-openhands browser click 'role=option[name="English"]', control-openhands browser click 'testid=submit-button', control-openhands browser reload, control-openhands browser wait 'testid=submit-button'; control-openhands browser text 'testid=submit-button' reads Save Changes and control-openhands api GET /api/settings --pick misc_settings.app_preferences.language is en.
Backend switch in placeF16.backend-switchMapped behavior
Scenario F16--backend-switch-in-place · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- SetupArrange the prerequisite backend state through the API.
- DoOpen /settings/app.
- DoReload the page.
- DoChoose “Backend selector”.
- DoChoose “Backend selector” / “Connected QA_Second”.
- WaitWait for “Environment switch overlay” to disappear.
- DoEnter “QA Local Edit” in “Git user name”.
- SetupUse the phone viewport.
- DoUse browser mouse click as specified in the exact recipe.
Expect
switching the active backend in the backend selector while this page is open shows the new backend's stored preferences (the footer already names it), also when switching back to a backend the page has already loaded; an unsaved edit is dropped on switch.
Exact CLI recipe & bug checks
Needs a second backend with a different Git username: the second stack from F25's Preconditions added as QA_Second (F25 Add bullet), arranged with control-openhands api PATCH /api/settings --run "$OH_VERIFY_RUN_2" --write --data '{"misc_settings_diff":{"app_preferences":{"git_user_name":"QA Second Git"}}}'. With Local active, run control-openhands browser goto /settings/app and control-openhands browser reload; browser value 'testid=git-user-name-input' is openhands. Switch without leaving the page: control-openhands browser click 'testid=backend-selector', control-openhands browser click 'testid=backend-selector >> role=option[name="Connected QA_Second"]', control-openhands browser wait 'testid=environment-switch-overlay' --state detached --timeout 10000, wait a few seconds: the value is QA Second Git and browser text 'testid=backend-synced-settings-badge' names QA_Second (a first visit to a backend loads fresh). Switch back the same way to Connected Local: the value is openhands beside synced from Local backend, and it keeps following the active backend on further switches. The form remounts per backend, so an unsaved edit is dropped: on Local run control-openhands browser fill 'testid=git-user-name-input' 'QA Local Edit' (browser enabled 'testid=submit-button' is true), switch to QA_Second (QA Second Git, Save false) and back (openhands, Save false); control-openhands api GET /api/settings --pick misc_settings.app_preferences.git_user_name is unchanged. At browser viewport phone the selector is in the drawer (testid=sidebar-mobile-menu-toggle, then 'testid=sidebar-mobile-drawer >> testid=backend-selector' and role=option[name="Connected QA_Second"]; close the drawer with browser mouse-click 370 400) and the field follows the same way. Switch back to Local and remove QA_Second afterwards (F25).
After each groupF16 · setup / cleanupSupport step
Scenario F16--after-each-group · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoAfter the family,
control-openhands browser errors --app-onlyreportsappErrors: 0. Requests toregistry.npmjs.org(update check) and Google Fonts fail through the sandbox proxy; they are not app errors.
Expect
Complete this support step before continuing to the next scenario.
Exact CLI recipe & bug checks
After the family, control-openhands browser errors --app-only reports appErrors: 0. Requests to registry.npmjs.org (update check) and Google Fonts fail through the sandbox proxy; they are not app errors.
Hard Do Not TrackF16.analyticsMapped behavior
Scenario F16--hard-do-not-track · Permalink · Source
Given
Use F16’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoUse browser storage as specified in the exact recipe.
Expect
the "Send anonymous usage data" switch saves with the form and sets browser telemetry consent to granted/denied.
Exact CLI recipe & bug checks
With the harness build's VITE_DO_NOT_TRACK=1, open /settings/app, read browser network --external --filter 'posthog|z.openhands.dev' and browser storage. Grant analytics through the real switch and Save, reload, inspect those again, then revoke and Save to restore. Expected: zero PostHog requests and no PostHog storage key, including after opt-in. This checks the build flag; browser-only DNT and builds without it have separate paths.
Family notes, prerequisites for known issues & limits
- 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 clickingsidebar-settings-/settings/secretsand back, shows the storedopenhandsagain;browser dialogsis 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.languagethe French Save toastsSettings saved, the Arabic Save toastsParamètres enregistrés, and the English restore toastsتم حفظ الإعدادات, sowait-text 'Settings saved'times out on the last two. Assert withbrowser text 'testid=submit-button'after a reload, or read the toast withbrowser 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'sSearch commandsname, 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èlesandContexte de l'agentin French), because they reuse the settings navigation's titles. Do not treat the English ones as a language switch failure; the sidebar trigger'sSearch commandsis translated. - In Arabic (
dir="rtl") a switch that is on draws its thumb outside its track (onshow-getting-started-checklist-switchthe track spansx778–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.checkedthrougheval. role=option[name^=...]is not supported by the selector parser; userole=option >> has-text=<profile>for profile options whose names include the model.conversation startleaves the browser on the conversation page, which also has atestid=submit-button;goto /settings/appbefore 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-modelproves the preference is stored and a title appears, not which model wrote it. onboard --skipstores analytics off, so a fresh run starts with the switch unchecked even though the code defaults to on when the preference is missing.browser storagelistslocalStorage, andbrowser storage --sessionlistssessionStorage, where the voice-input key lives. With--valuesthat key prints as<redacted>, so the recipe reads its dummy value witheval. 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_profilethat this page shows as Automatic. - The Title generation model combobox has a clear (×) button with no accessible name (
- buttonin the snapshot, visible as × at phone width). At desktop width it only takes clicks afterbrowser hoveron the input (F16.title-model-clear); otherwise use the Automatic option. browser evalwraps its argument as an expression: statements separated by;fail withSyntaxError: Unexpected token ';'; wrap them in(() => { ...; return x; })().- With
service stop agent-server, abrowser reloadof/settings/appshows the backend-unavailable screen (agent-server-onboarding-screen, Manage backends), not the form: stop the service only after the page is loaded, andrestartafterwards. - 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'reportstotal0, 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 withlaunch --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 suggestionsbutton. If both ever match, Playwright's strict mode fails the click instead of pressing the wrong one; report it as harness drift.
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.