EN / field notes OpenHands feature map

OpenHands / F14

Secrets

Secrets are named values the agent can use during conversations. A user lists, adds, edits and deletes them under Settings → Secrets; saved values are never shown again, and every new conversation receives them as environment variables.

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

How to get to it

  • Sidebar settings gear (backend-selector-settings-link), then Secrets in the settings navigation (sidebar-settings-/settings/secrets).
  • Direct URL /settings/secrets.
  • Command menu (Control+k or Meta+k, or command-menu-trigger in the sidebar): search "Secrets", choose Secrets settings.

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).
  • No secret named QA_TMP_SECRET, QA_AGENT_SECRET, QA_RENAMED_SECRET or QA_BACK_SECRET exists (control-openhands api GET /api/settings/secrets).
  • F14.agent-access, F14.rename and F14.edit-value need an active LLM profile (control-openhands llm preset deepseek).

Behavior inventory

11 stable behavior IDs and their expected behavior
  • F14.list the Secrets page lists each secret's name and description with Edit and Delete actions; the launcher-seeded OPENHANDS_AUTOMATION_API_KEY is always present. Read recipe ↓
  • F14.create adding a secret persists it; it is listed after a reload. Read recipe ↓
  • F14.create-validation duplicate names, names that break ^[a-zA-Z][a-zA-Z0-9_]{0,63}$ and empty values are refused with a message. Read recipe ↓
  • F14.edit editing the description with the value left blank keeps the stored value; Save stays disabled until something changes. Read recipe ↓
  • F14.delete deleting asks for confirmation; Cancel keeps the row, Confirm removes it for good. Read recipe ↓
  • F14.agent-access a conversation started after the secret was saved sees it as an environment variable. Read recipe ↓
  • F14.phone the page and its forms fit a 390 px viewport without horizontal overflow. Read recipe ↓
  • F14.form-back the Add and Edit forms replace the list with an Add a Secret / Edit a Secret title and a Back button; Back (like Cancel) returns to the list and discards the draft. Read recipe ↓
  • F14.rename the Edit form's name is editable; renaming with the value left blank keeps the stored value under the new name, and renaming onto an existing name is refused with Secret already exists. Read recipe ↓
  • F14.edit-value typing a value in the Edit form (labelled "Secret Value (leave blank to preserve the existing value)") replaces the stored value for new conversations. Read recipe ↓
  • F14.delete-escape Escape closes the delete confirmation without deleting. Read recipe ↓

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 the list #

  1. Note
    Navigate from the sidebar.
  2. Do
    control-openhands browser click 'testid=backend-selector-settings-link'
  3. Do
    control-openhands browser click 'testid=sidebar-settings-/settings/secrets'
  4. Check
    control-openhands browser count 'testid=secret-item >> has-text=OPENHANDS_AUTOMATION_API_KEY'
  5. Expect
    The count is 1 and the URL is /settings/secrets.
Secrets page lists DOCS_DEMO_SECRET with its description and edit and delete controls; no secret value is displayed.
The saved secret appears after reload, while the list keeps its value hidden. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Secrets page lists DOCS_DEMO_SECRET with its description and edit and delete controls; no secret value is displayed.

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. The add form used a harmless dummy value. The add form shows this value in plain text; the saved list does not display values.

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 /settings/secrets
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait testid=add-secret-button
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click testid=add-secret-button
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser fill 'testid=add-secret-form >> testid=name-input' DOCS_DEMO_SECRET
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser fill 'testid=add-secret-form >> testid=value-input' dummy-value-for-documentation
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser fill 'testid=add-secret-form >> testid=description-input' 'Disposable example for the feature map'
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click 'testid=add-secret-form >> testid=submit-button'
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser reload
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait 'testid=secret-item >> has-text=DOCS_DEMO_SECRET'
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser screenshot --feature F14.list --name secrets-list

Command-menu entry #

  1. Do
    control-openhands browser goto /

    (the previous bullet left /settings/secrets, which would make the URL check pass vacuously), then

  2. Do
    control-openhands browser press Control+k
  3. Do
    control-openhands browser type 'testid=command-menu >> role=combobox' Secrets
  4. Do
    control-openhands browser press Enter
  5. Check
    control-openhands browser url
  6. Expect
    The URL ends in /settings/secrets.

Create #

  1. Do
    control-openhands browser click 'testid=add-secret-button'
  2. Do
    control-openhands browser fill 'testid=add-secret-form >> testid=name-input' QA_TMP_SECRET
  3. Do
    control-openhands browser fill 'testid=add-secret-form >> testid=value-input' dummy-value-123
  4. Do
    control-openhands browser fill 'testid=add-secret-form >> testid=description-input' 'QA dummy'
  5. Do
    control-openhands browser click 'testid=add-secret-form >> testid=submit-button'
  6. Do
    control-openhands browser reload
  7. Check
    control-openhands browser count 'testid=secret-item >> has-text=QA_TMP_SECRET'
  8. Expect
    The count is 1 after the reload.
Add secret form contains DOCS_DEMO_SECRET, the visible dummy value dummy-value-for-documentation and a disposable example description.
Create a named secret with a description. This example uses a harmless dummy value; the add form displays the value as plain text. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Add secret form contains DOCS_DEMO_SECRET, the visible dummy value dummy-value-for-documentation and a disposable example description.

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. The add form used a harmless dummy value. The add form shows this value in plain text; the saved list does not display values.

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 /settings/secrets
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser wait testid=add-secret-button
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser click testid=add-secret-button
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser fill 'testid=add-secret-form >> testid=name-input' DOCS_DEMO_SECRET
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser fill 'testid=add-secret-form >> testid=value-input' dummy-value-for-documentation
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser fill 'testid=add-secret-form >> testid=description-input' 'Disposable example for the feature map'
OH_VERIFY_RUN="$OH_VERIFY_RUN" control-openhands browser screenshot --feature F14.create --name add-secret

Duplicate name #

  1. Do
    control-openhands browser click 'testid=add-secret-button'
  2. Note
    fill name-input with QA_TMP_SECRET and value-input with other as above, click testid=add-secret-form >> testid=submit-button, then
  3. Check
    control-openhands browser snapshot 'testid=add-secret-form'
  4. Expect
    The snapshot shows the paragraph Secret already exists and the form stays open.

Invalid name #

  1. Note
    In the same form run
  2. Do
    control-openhands browser fill 'testid=add-secret-form >> testid=name-input' 'bad name!'
  3. Note
    click submit, then
  4. Do
    control-openhands browser eval "document.querySelector('[data-testid=add-secret-form] [data-testid=name-input]').validationMessage"
  5. Expect
    The value is the browser's pattern message (Please match the requested format. in Chromium) and no secret is created (api GET /api/settings/secrets).
  6. Note
    Close with
  7. Do
    control-openhands browser click 'testid=add-secret-form >> testid=cancel-button'

Empty value #

  1. Do
    control-openhands browser click 'testid=add-secret-button'
  2. Note
    fill testid=add-secret-form >> testid=name-input with QA_EMPTY_VALUE and leave value-input empty.
  3. Check
    control-openhands browser enabled 'testid=add-secret-form >> testid=submit-button'

    is false, and

  4. Check
    control-openhands browser eval "document.querySelector('[data-testid=add-secret-form] [data-testid=value-input]').validationMessage"

    is Please fill out this field. Close with testid=add-secret-form >> testid=cancel-button; api GET /api/settings/secrets lists no QA_EMPTY_VALUE.

Edit keeps the value #

  1. Do
    control-openhands browser click 'testid=secret-item >> has-text=QA_TMP_SECRET >> testid=edit-secret-button'
  2. Check
    control-openhands browser value 'testid=edit-secret-form >> testid=value-input'

    (empty) and

  3. Check
    control-openhands browser enabled 'testid=edit-secret-form >> testid=submit-button'

    (false).

  4. Note
    Fill testid=edit-secret-form >> testid=description-input with QA dummy edited; enabled turns true.
  5. Note
    Click testid=edit-secret-form >> testid=submit-button,
  6. Do
    control-openhands browser reload
  7. Check
    control-openhands browser text 'testid=secret-item >> has-text=QA_TMP_SECRET'
  8. Expect
    The row text is QA_TMP_SECRET and QA dummy edited separated by a tab ("QA_TMP_SECRET\tQA dummy edited" in the JSON output).

Delete with confirmation #

  1. Do
    control-openhands browser click 'testid=secret-item >> has-text=QA_TMP_SECRET >> testid=delete-secret-button'
  2. Note
    the dialog testid=confirmation-modal reads Are you sure you want to delete this key?.
  3. Do
    control-openhands browser click 'testid=confirmation-modal >> testid=cancel-button'
  4. Note
    the row count stays 1.
  5. Note
    Repeat the delete click, then
  6. Do
    control-openhands browser click 'testid=confirmation-modal >> testid=confirm-button'
  7. Do
    control-openhands browser reload
  8. Check
    control-openhands browser count 'testid=secret-item >> has-text=QA_TMP_SECRET'
  9. Expect
    The count is 0.

Agent receives the secret #

  1. Note
    Create QA_AGENT_SECRET with value qa-dummy-4821 through the form as in Create.
  2. Wait
    control-openhands conversation start --prompt "Run exactly this command and reply with only its output: python3 -c \"import os; print(os.environ.get('QA_AGENT_SECRET') == 'qa-dummy-4821')\"" --wait --timeout 240
  3. Check
    control-openhands conversation events <id> --kinds ObservationEvent
  4. Expect
    The terminal observation's text is True.
  5. Note
    Assert the observation, not the agent's reply: deepseek-flash often declines to echo anything about a secret (see Gotchas).
  6. Note
    Keep QA_AGENT_SECRET for Rename and Replace value below; they delete it.

Phone layout #

  1. Do
    control-openhands browser goto /settings/secrets

    (conversation start left the browser on the conversation),

  2. Do
    control-openhands browser viewport phone
  3. Check
    control-openhands browser bbox 'testid=secrets-settings-screen'
  4. Do
    control-openhands browser screenshot --feature F14.phone --name list
  5. Note
    insideViewport is true and pageHorizontalOverflow is false; the screenshot shows the table with Edit and Delete icons.
  6. Do
    control-openhands browser click 'testid=add-secret-button'
  7. Check
    control-openhands browser bbox 'testid=add-secret-form'

    (insideViewport true, no page overflow) and

  8. Do
    control-openhands browser screenshot --feature F14.phone --name add-form
  9. Note
    cancel the form.
  10. Note
    Return with
  11. Do
    control-openhands browser viewport desktop

Back discards the draft #

  1. Note
    On /settings/secrets run
  2. Do
    control-openhands browser click 'testid=add-secret-button'
  3. Check
    control-openhands browser text 'testid=secret-editor-title'

    (Add a Secret).

  4. Note
    Fill testid=add-secret-form >> testid=name-input with QA_BACK_SECRET and value-input with dummy, then
  5. Do
    control-openhands browser click 'testid=back-to-secrets'
  6. Check
    control-openhands browser count 'testid=add-secret-form'

    is 0, the list is back (browser count 'testid=secret-item' > 0) and

  7. Check
    control-openhands api GET /api/settings/secrets

    lists no QA_BACK_SECRET.

  8. Note
    Reopening Add shows an empty name-input (browser value); leave with testid=back-to-secrets.
  9. Expect
    The Edit form shows Edit a Secret in the same secret-editor-title.

Rename keeps the value #

  1. Note
    Needs QA_AGENT_SECRET from Agent receives the secret.
  2. Do
    control-openhands browser goto /settings/secrets
  3. Do
    control-openhands browser click 'testid=secret-item >> has-text=QA_AGENT_SECRET >> testid=edit-secret-button'
  4. Note
    fill testid=edit-secret-form >> testid=name-input with OPENHANDS_AUTOMATION_API_KEY and click testid=edit-secret-form >> testid=submit-button:
  5. Check
    control-openhands browser snapshot 'testid=edit-secret-form'

    shows the paragraph Secret already exists and the form stays open.

  6. Note
    Fill name-input with QA_RENAMED_SECRET (value left blank), click submit,
  7. Do
    control-openhands browser reload
  8. Note
    browser count 'testid=secret-item >> has-text=QA_RENAMED_SECRET' is 1, the QA_AGENT_SECRET count is 0, and api GET /api/settings/secrets lists only the new name.
  9. Wait
    control-openhands conversation start --prompt "Run exactly this command and reply with only its output: python3 -c \"import os; print(os.environ.get('QA_RENAMED_SECRET') == 'qa-dummy-4821', 'QA_AGENT_SECRET' in os.environ)\"" --wait --timeout 240
  10. Check
    control-openhands conversation events <id> --kinds ObservationEvent
  11. Note
    : the observation is True False.

Replace the value #

  1. Do
    control-openhands browser goto /settings/secrets
  2. Do
    control-openhands browser click 'testid=secret-item >> has-text=QA_RENAMED_SECRET >> testid=edit-secret-button'
  3. Check
    control-openhands browser snapshot 'testid=edit-secret-form'

    (the value textbox is named Secret Value (leave blank to preserve the existing value)).

  4. Note
    Fill testid=edit-secret-form >> testid=value-input with qa-dummy-7350; browser enabled 'testid=edit-secret-form >> testid=submit-button' is true.
  5. Note
    Click submit; browser count 'testid=edit-secret-form' is 0.
  6. Note
    Start a conversation with the prompt Run exactly this command and reply with only its output: python3 -c "import os; print(os.environ.get('QA_RENAMED_SECRET') == 'qa-dummy-7350')" (quoted as above) and read its ObservationEvent: True.

Escape cancels delete #

  1. Do
    control-openhands browser goto /settings/secrets
  2. Do
    control-openhands browser click 'testid=secret-item >> has-text=QA_RENAMED_SECRET >> testid=delete-secret-button'
  3. Do
    control-openhands browser press Escape
  4. Check
    control-openhands browser count 'testid=confirmation-modal'

    (0) and browser count 'testid=secret-item >> has-text=QA_RENAMED_SECRET' (1).

  5. Note
    Clean up: delete QA_RENAMED_SECRET with delete-secret-button → testid=confirmation-modal >> testid=confirm-button, browser reload; api GET /api/settings/secrets lists only OPENHANDS_AUTOMATION_API_KEY.

Gotchas and known limits

  • Settings nav test ids embed the route, slashes included: sidebar-settings-/settings/secrets. Quote the selector as written.
  • The value editor is a plain textarea, not a password field: use dummy values only, and never ask the agent to print a real secret.
  • Edit forms intentionally show an empty value; that is not data loss.
  • The invalid-name check is native browser validation. It shows a tooltip that screenshots do not capture, and it leaves any earlier Secret already exists paragraph on screen. Assert with validationMessage and the API, not the paragraph.
  • The model is the weak link in the agent checks, not the product: asked to print a secret's length or last characters, deepseek-flash replies "I can't run that one" and runs nothing, and even for the equality check it may refuse to repeat True in its reply. Use the equality prompt and read the terminal ObservationEvent; conversation events truncates message text to about 160 characters.
  • The conversation Overview has a Secrets drawer (conversation-overview-secrets-panel, conversation-overview-secrets-add-button) built on the same form, but no UI opens it: the Overview "..." menu (conversation-overview-ellipsis) offers Workspace and Git sections only. It is not a user entry point today.
  • There is no empty state to reach safely: secrets-empty renders only with zero secrets, which means deleting the launcher-seeded key (see below).
  • Secrets reach only conversations started after the save. Reuse an older conversation and the variable is missing.
  • At 390 px the name column truncates long names (OPEN…); use browser text on the row rather than the screenshot to read a name.
  • OPENHANDS_AUTOMATION_API_KEY is seeded by the launcher for automations; deleting it breaks F18/F19 runs in the same stack.

Source paths: src/routes/secrets-settings.tsx, src/components/features/settings/secrets-settings/, src/api/secrets-service.ts.