EN / field notes OpenHands feature map

OpenHands / F23

Automation detail, runs and editing

The detail page (/automations/<id>) shows one automation: a Back to Automations link, a header with the name, an Active/Inactive badge, Run now, an on/off switch and a kebab (Export, Tarball, Edit, Turn on/off, Delete); a Why this is paused banner when it was turned off; the Prompt (or, for a script automation, the Script bundle); Configuration (repositories, trigger, schedule or event source/type/filter, LLM profile); a Plugins card when it has plugins; an Activity card (Created, Last run); and the Activity Log of runs with status, summary, cost, a logs dialog, a link to each run's conversation and JSON/CSV export. The Edit automation dialog changes name, prompt, agent and LLM profile, timeout and the schedule.

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

How to get to it

  • Dashboard (/automations, F21): click a card's heading, a list row link, or the kebab Automation actions → View; a sparkline bar opens /automations/<id>?run=<runId> (F21.sparkline-deeplink).
  • Direct URL /automations/<id> and /automations/<id>?run=<runId>. Get <id> from control-openhands browser url after opening a card, or from control-openhands api GET /api/automation/v1; get <runId> from control-openhands api GET /api/automation/v1/<id>/runs.
  • Edit automation: the detail kebab Edit (exercised here); the dashboard kebab Edit opens the same dialog (F21.edit).
  • Browser Back from a run's conversation returns to the detail page.
  • The detail page has no command-menu entry and no keyboard shortcut.

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 for the runs: control-openhands llm preset deepseek (--api-key-file <file> when the key is in a file); the edit recipes also expect deepseek-pro in the LLM profile list.
  • No QA_F23 automations and no Daily news digest exist (control-openhands api GET /api/automation/v1).
  • Arrange three fixtures through the API (arrange only; F22 owns creating automations in the UI). Each prints the new automation in body (body.id is <id>): control-openhands api POST /api/automation/v1/preset/prompt --write --data '{"name":"QA_F23 Pong","prompt":"Reply with the single word: pong. Do not run any tools.","trigger":{"type":"cron","schedule":"0 9 * * *","timezone":"UTC"},"enabled":false}', control-openhands api POST /api/automation/v1/preset/prompt --write --data '{"name":"QA_F23 Fail","prompt":"Reply with the single word: pong.","trigger":{"type":"cron","schedule":"0 0 1 1 *","timezone":"UTC"},"enabled":true}', control-openhands api POST /api/automation/v1/preset/plugin --write --data '{"name":"QA_F23 Event","prompt":"QA line 1: reply pong.\nQA line 2: reply pong.\nQA line 3: reply pong.\nQA line 4: reply pong.\nQA line 5: reply pong.\nQA line 6: reply pong.\nQA line 7: reply pong.\nQA line 8: reply pong.\nQA line 9: reply pong.\nQA line 10: reply pong.\nQA line 11: reply pong.\nQA line 12: reply pong.\nQA line 13: reply pong.\nQA line 14: reply pong.\nQA line 15: reply pong.","plugins":[{"source":"github:qa-example/qa-plugin"}],"repos":[{"url":"https://github.com/qa-example/qa-repo","ref":"main"}],"trigger":{"type":"event","source":"github","on":"pull_request.opened","filter":"pull_request.draft == `false` && pull_request.base.ref == repository.default_branch"},"enabled":false}'. Pong is a daily preset (0 9 * * *) created off; Fail is a yearly custom cron (0 0 1 1 *) created on, so it never fires by itself; Event never fires (no GitHub webhooks).
  • Desktop viewport. Open a fixture's page the way a user does: control-openhands browser goto /automations, then control-openhands browser click '[data-testid^="automation-card-"] >> has-text=<name> >> role=heading' --expect-url '/automations/[0-9a-f-]+'. Below this is called "open <name>".

Behavior inventory

39 stable behavior IDs and their expected behavior
  • F23.open-detail a dashboard card, a list row, the kebab View and a direct URL open /automations/<id>; browser Back from a run's conversation returns to it. Read recipe ↓
  • F23.loading detail-skeleton stands in while the automation loads. Read recipe ↓
  • F23.not-found an unknown id shows Automation not found with "The automation you're looking for doesn't exist or has been deleted." and the back link. Read recipe ↓
  • F23.backend-unavailable with the automation service down, the page shows Automations Unavailable and Retry; Retry recovers once the service is back. Read recipe ↓
  • F23.load-error when the health check is still good but the automation request fails, the page shows Failed to load automations and Retry. Read recipe ↓
  • F23.header the header shows the name, the Active/Inactive badge, Run now (disabled while inactive), the switch (Turn on/Turn off) and the Automation actions kebab with Export, Tarball, Edit, Turn on/Turn off and Delete. Read recipe ↓
  • F23.toggle the header switch flips the enabled flag at once (no confirmation); the badge, switch and Run now follow and survive a reload. Read recipe ↓
  • F23.disabled-reason after Turn off, the banner automation-disabled-reason-banner reads Why this is paused / Turned off manually / Paused <time>; it is gone while active. Read recipe ↓
  • F23.run-now Run now dispatches a run with the toast Automation dispatched and a Pending row appears in the log (the button reads Starting… only for the few ms of the request). Read recipe ↓
  • F23.run-now-error a failed dispatch shows exactly one error toast with the API message; when the automation is gone (Automation not found), the page then shows the Automation not found state. Read recipe ↓
  • F23.run-status-polling while a run is Pending or Running the log refetches every 3 s, so Pending → Running → Successful/Failed, the summary, the cost and the conversation link appear without a reload; the Activity card's Last run updates too. Read recipe ↓
  • F23.activity-log Activity Log shows No activity yet with Export disabled before the first run, then one row per run (newest first): start time, summary, cost ($0.0000 format, run-cost), a logs icon, status badge (run-status-icon-<status>). Read recipe ↓
  • F23.run-task-outcome a Completed run whose agent finished with a task outcome other than success shows that outcome instead of Successful: blocked and partial_success read Blocked / Partial with run-status-icon-warning, unknown and any unrecognised status string read Needs review (run-status-icon-needs-review), and a finish response without a string status (summary only, custom fields only, a non-string status, or not an object) is expected to read Successful, and an outcome_summary, when it has one, is still shown (#17682); the row summary is the agent's outcome_summary, and the Logs dialog's Task section repeats it. Read recipe ↓
  • F23.activity-log-more with more than 20 runs a Load more button grows the page by 20. Read recipe ↓
  • F23.run-no-conversation a finished run without a conversation reads No Conversation; only a script run (no agent) gets "No conversation — this run executed a script. Use View logs for its output." Read recipe ↓
  • F23.run-linked-conversation a run row with a conversation is a link labelled View conversation for run at <time> to /conversations/<conversation_id>, whose title is <automation name> — <UTC timestamp>. Read recipe ↓
  • F23.run-logs-modal the row's terminal icon (View logs) opens the Logs dialog with Run, Task (status, summary, automation-task-metadata) and System sections and Output/Error tabs showing the run command's stdout/stderr; Escape and the X close it without navigating. Read recipe ↓
  • F23.debug-with-openhands in the Logs dialog of a Failed run, Debug with OpenHands starts a conversation seeded with the automation, the error and the run id, and opens it. Read recipe ↓
  • F23.activity-log-export Export JSON / Export CSV download <name-slug>.activity-log.json|csv with every run, including conversation_url. Read recipe ↓
  • F23.prompt-collapse a prompt taller than about 10 lines is clipped to 240 px with a fade (automation-prompt-fade) and View More; View Less collapses it again. Read recipe ↓
  • F23.configuration a cron automation shows Trigger Schedule, the Schedule and LLM profile (Active profile or the profile name).
  • F23.configuration-event an event automation shows Trigger Event, Event Source, Event Type and Filter, truncated at 60 characters with Show more/Show less. Read recipe ↓
  • F23.plugins-repo an automation created with repositories and plugins shows Repositories (one owner/repo row each, with a branch badge for its ref) and a Plugins card with one chip per plugin. Read recipe ↓
  • F23.script-section a script (tarball) automation shows Script with the entrypoint and the bundle's files in place of the Prompt. Read recipe ↓
  • F23.export kebab Export downloads <name-slug>.automation.json (version, kind: "automation", spec); an automation's first repository and its plugins go into spec as repository, branch and plugins. Read recipe ↓
  • F23.tarball kebab Tarball downloads the automation's code bundle. Read recipe ↓
  • F23.delete kebab Delete asks Delete "<name>"? This cannot be undone.; Cancel keeps it, Delete removes it and returns to /automations. Read recipe ↓
  • F23.edit-open kebab Edit opens Edit automation prefilled with Name, Prompt ("Edits apply to future runs only."), Agent profile, LLM profile, Timeout (seconds) with its hint, and the schedule. Read recipe ↓
  • F23.edit-close X, Cancel, a backdrop click and Escape close the dialog without saving; reopening shows the stored values. Read recipe ↓
  • F23.edit-validation an empty name (Name is required), a bad timeout (Timeout must be a valid number / must be positive / cannot exceed 1800 seconds) and a bad cron (Enter a valid cron expression / This cron expression will never run) are refused inline. Read recipe ↓
  • F23.edit-save Save with no change just closes (no request); a change sends one PATCH, shows Saving..., the toast Automation updated, and the detail page and API reflect it. Read recipe ↓
  • F23.edit-schedule Frequency offers Daily, Weekdays (Mon–Fri) and Weekly; Weekly adds Day of week; with Time of day they are saved as a cron expression and read back on reopen. Read recipe ↓
  • F23.edit-time-cleared clearing Time of day on a preset schedule and saving is refused with a message instead of being dropped. Read recipe ↓
  • F23.edit-cron a custom cron schedule shows Frequency Custom and Time of day disabled, and an editable Cron expression. Read recipe ↓
  • F23.edit-event-readonly for an event automation the dialog shows Trigger Event, Event Source, Event Type and Filter as read-only text and no schedule fields. Read recipe ↓
  • F23.edit-save-error a failed save shows exactly one error toast with the API message and keeps the dialog open. Read recipe ↓
  • F23.phone at 390×844 the detail page has no horizontal overflow, and the Edit dialog fits with its title, X and Save reachable. 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 from the dashboard #

  1. Note
    Open QA_F23 Pong, then
  2. Check
    control-openhands browser url

    (/automations/<id>),

  3. Check
    control-openhands browser snapshot
  4. Do
    control-openhands browser screenshot --feature F23.header --name inactive
  5. Expect
    The snapshot shows link Back to Automations, heading QA_F23 Pong (level 1), Inactive, button "Run now" [disabled], switch "Turn on" and button "Automation actions", then the Prompt, Configuration, Activity (Last run Never) and Activity Log (No activity yet, Export JSON [disabled], Export CSV [disabled]) sections.
  6. Do
    control-openhands browser click 'role=button[name="Automation actions"]'
  7. Check
    control-openhands browser snapshot 'role=list >> has-text=Tarball'
  8. Note
    : buttons Export, Tarball, Edit, Turn on, Delete.
  9. Do
    control-openhands browser press Escape
  10. Note
    closes it (browser count 'role=list >> has-text=Tarball' is 0).
QA_F23 Pong automation detail with Inactive status, prompt, configuration and empty activity.
Opening a card reveals its prompt, configuration and activity without running it. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Detail page shows QA_F23 Pong, Inactive, configuration and no activity.

Disabled local dummy fixture; runs, logs, editing and debug not asserted.

How this screenshot was taken

agent server: 1.53.0 · automation: 1.19.0 (launcher default) · canvas: 1.26.0

control-openhands browser goto /automations
control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F23 Pong >> role=heading' --expect-url '/automations/[0-9a-f-]+'
control-openhands browser screenshot --feature F23.open-detail --name inactive-detail

Other ways in #

  1. Check
    control-openhands browser click 'role=link[name="Back to Automations"]' --expect-url '/automations(\?|$)'
  2. Do
    control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F23 Pong >> role=button[name="Automation actions"]'
  3. Do
    control-openhands browser click 'role=list >> has-text=Export >> role=button[name="View"]' --expect-url '/automations/[0-9a-f-]+'
  4. Note
    land on the same /automations/<id>.
  5. Note
    List view too:
  6. Do
    control-openhands browser goto /automations
  7. Do
    control-openhands browser click 'testid=automations-view-toggle'
  8. Do
    control-openhands browser click 'testid=automations-view-toggle-list'
  9. Do
    control-openhands browser click '[data-testid^="automation-list-row-"] >> has-text=QA_F23 Pong >> role=link >> nth=0' --expect-url '/automations/[0-9a-f-]+'
  10. Note
    then restore grid (goto /automations, the toggle, testid=automations-view-toggle-grid; browser eval "localStorage.getItem('openhands-automations-view')" is "grid").
  11. Do
    control-openhands browser goto /automations/<id>

    (direct URL) shows the same page.

States: loading and not found #

  1. Do
    control-openhands browser goto /automations/00000000-0000-0000-0000-000000000000
  2. Note
    and at once
  3. Check
    control-openhands browser count 'testid=detail-skeleton'

    (1).

  4. Wait
    control-openhands browser wait-text 'Automation not found'
  5. Note
    succeeds after about 7 s, detail-skeleton is then 0, and
  6. Check
    control-openhands browser snapshot 'role=paragraph >> text=Automation not found >> xpath=..'

    lists both paragraphs and link Back to Automations.

  7. Note
    Screenshot with --feature F23.not-found --name not-found;
  8. Check
    control-openhands browser click 'role=link[name="Back to Automations"]' --expect-url '/automations(\?|$)'
  9. Note
    browser errors --app-only lists the four induced 404s on /api/automation/v1/00000000-....

Backend unavailable #

  1. Note
    On QA_F23 Pong:
  2. Do
    control-openhands service stop automation
  3. Do
    control-openhands browser goto /automations/<id>
  4. Wait
    control-openhands browser wait-text 'Automations Unavailable'
  5. Check
    control-openhands browser text 'role=button[name="Retry"] >> xpath=..'
  6. Note
    : Automations Unavailable, The automations backend is not available right now. Please try again later or check that the automation service is running., Retry.
  7. Do
    control-openhands restart
  8. Do
    control-openhands browser click 'role=button[name="Retry"]'
  9. Wait
    control-openhands browser wait 'role=heading[name="QA_F23 Pong"]'
  10. Note
    : the page recovers without a reload.

Load error #

  1. Do
    control-openhands browser goto /automations

    (a fresh load, so the detail is not cached), wait for the card,

  2. Do
    control-openhands service stop automation
  3. Note
    then within 30 s
  4. Do
    control-openhands browser click '[data-testid^="automation-card-"] >> has-text=QA_F23 Pong >> role=heading' --expect-url '/automations/[0-9a-f-]+'
  5. Wait
    control-openhands browser wait-text 'Failed to load automations'

    (after the retries, about 7 s).

  6. Check
    control-openhands browser text 'role=button[name="Retry"] >> xpath=..'

    is Failed to load automations / Retry.

  7. Do
    control-openhands restart
  8. Note
    click Retry, and the detail page returns.

Turn on with the switch #

  1. Note
    On Pong run
  2. Do
    control-openhands browser click 'role=switch[name="Turn on"]'
  3. Wait
    control-openhands browser wait 'testid=active-status-badge-active' --timeout 5000
  4. Check
    control-openhands browser count 'testid=confirmation-modal'

    (0: no confirmation).

  5. Note
    After
  6. Do
    control-openhands browser reload
  7. Check
    control-openhands browser text 'testid=active-status-badge-active'

    is Active,

  8. Check
    control-openhands browser attr 'role=switch[name="Turn off"]' aria-checked

    is true and

  9. Check
    control-openhands browser enabled 'role=button[name="Run now"]'

    is true.

Run now and live status #

  1. Do
    control-openhands browser click 'role=button[name="Run now"]'
  2. Note
    then at once
  3. Check
    control-openhands browser toasts

    (Automation dispatched) and

  4. Check
    control-openhands browser testids 'testid=automation-activity-log'

    (run-status-icon-pending).

  5. Note
    Without reloading, repeat
  6. Check
    control-openhands browser text 'testid=automation-activity-log'
  7. Note
    every few seconds: Pending, then Running (run-status-icon-running) after about 5-20 s, then about 30-60 s after dispatch <weekday, month day, year at h:mm AM>, the agent's summary (for example Replied with the single word "pong" as requested, without running any tools.), a cost such as $0.0018 and Successful (run-status-icon-completed, run-cost with title Cost).
  8. Check
    control-openhands browser text 'role=heading[name="Activity"][exact] >> xpath=ancestor::div[2]'

    (without [exact] it also matches Activity Log and returns two texts) reads Last run / Just now.

  9. Note
    Screenshot with
  10. Check
    control-openhands browser screenshot --feature F23.run-status-polling --name completed

Linked conversation #

  1. Check
    control-openhands browser attr 'testid=automation-activity-log >> role=link' href

    (/conversations/<conversation_id>) and ... aria-label (View conversation for run at <time>), then

  2. Do
    control-openhands browser click 'testid=automation-activity-log >> role=link' --expect-url '/conversations/'
  3. Check
    control-openhands browser text 'testid=conversation-name'
  4. Note
    : QA_F23 Pong — <YYYY-MM-DD HH:MM:SS> UTC.
  5. Do
    control-openhands browser back

    returns to the detail page (browser wait 'testid=automation-activity-log').

Run deep link #

  1. Note
    Get <runId> from
  2. Check
    control-openhands api GET /api/automation/v1/<id>/runs

    (body.runs[0].id).

  3. Do
    control-openhands browser goto '/automations/<id>?run=<runId>'
  4. Wait
    control-openhands browser wait 'testid=automation-run-highlight-<runId>'
  5. Check
    control-openhands browser attr 'testid=automation-run-highlight-<runId>' class

    (bg-focus/10).

  6. Note
    With ?run=00000000-0000-0000-0000-000000000000,
  7. Check
    control-openhands browser testids --filter highlight

    lists nothing.

Logs dialog #

  1. Do
    control-openhands browser click 'testid=automation-activity-log >> title=View logs'
  2. Check
    control-openhands browser url

    (unchanged: the click does not follow the row link) and

  3. Check
    control-openhands browser snapshot 'role=dialog'
  4. Note
    : dialog "Logs" with Run COMPLETED, Task Successful plus the summary and Task metadata (automation-task-metadata: the finish tool's extra fields, for example { "message": "pong", "summary": "Replied with the single word pong as requested" }; the keys vary with what the agent sent), System No system issues reported., and tabs Output [selected] / Error.
  5. Check
    control-openhands browser text 'testid=run-logs-output-stdout'

    is the run command's stdout: it starts [setup] Fetching SDK version from automation service and contains === EXECUTION MODE === and mode: LOCAL (the same text control-openhands api GET '/api/bash/bash_events/search?kind__eq=BashOutput&command_id__eq=<bash_command_id>' returns; bash_command_id from the runs API).

  6. Note
    Screenshot with --feature F23.run-logs-modal --name output.
  7. Do
    control-openhands browser click 'role=dialog >> role=tab[name="Error"]'
  8. Note
    selects Error (aria-selected true);
  9. Do
    control-openhands browser press Escape
  10. Note
    makes browser count 'role=dialog' 0.
  11. Note
    Request scope:
  12. Do
    control-openhands browser reload

    (a reopened dialog reuses its cached logs),

  13. Check
    control-openhands browser network --clear
  14. Note
    click testid=automation-activity-log >> title=View logs again and run
  15. Check
    control-openhands browser network --filter bash
  16. Note
    : only GET /api/bash/bash_events/search (server level, 200), no /api/conversations/<id>/bash/... although this run has a conversation.
  17. Note
    Escape.

Export the log #

  1. Do
    control-openhands browser click 'testid=activity-log-export-json'
  2. Check
    control-openhands browser downloads --last 1 --inspect
  3. Note
    : qa-f23-pong.activity-log.json with runs[] holding run_id, automation_name, trigger, start_time, end_time, duration_seconds, status COMPLETED, conversation_id and conversation_url (http://127.0.0.1:<port>/conversations/<id>).
  4. Do
    control-openhands browser click 'testid=activity-log-export-csv'
  5. Note
    and the same downloads call: qa-f23-pong.activity-log.csv with header run_id,automation_id,automation_name,trigger,start_time,end_time,duration_seconds,status,conversation_id,conversation_url,error,cost,phase.

Export and Tarball #

  1. Do
    control-openhands browser click 'role=button[name="Automation actions"]'
  2. Do
    control-openhands browser click 'role=list >> has-text=Tarball >> role=button[name="Export"]'
  3. Check
    control-openhands browser downloads --last 1 --inspect
  4. Note
    : qa-f23-pong.automation.json with "version": 1, "kind": "automation" and spec (name, trigger, enabled, prompt, model null, timeout 600, timezone).
  5. Note
    Reopen the kebab and click role=list >> has-text=Tarball >> role=button[name="Tarball"];
  6. Check
    control-openhands browser downloads --last 1

    lists QA_F23_Pong.tar (about 9 KB) and no toast appears.

  7. Expect
    The file is gzip data despite the name: tar -tzf <path> lists main.py, finish_tool_hook.py, prompt.txt, setup.sh.

Turn off and the paused banner #

  1. Note
    While Pong is active
  2. Check
    control-openhands browser count 'testid=automation-disabled-reason-banner'

    is 0.

  3. Note
    Open the kebab,
  4. Do
    control-openhands browser click 'role=list >> has-text=Tarball >> role=button[name="Turn off"]'
  5. Check
    control-openhands browser count 'testid=confirmation-modal'

    (0),

  6. Do
    control-openhands browser reload
  7. Check
    control-openhands browser text 'testid=automation-disabled-reason-banner'
  8. Note
    : Why this is paused / Turned off manually / Paused Just now (automation-disabled-reason-text, automation-disabled-reason-timestamp).
  9. Expect
    The badge reads Inactive and Run now is disabled.
  10. Note
    Screenshot with --feature F23.disabled-reason --name manual.

Edit fields and closing #

  1. Note
    On Pong run
  2. Do
    control-openhands browser click 'role=button[name="Automation actions"]'
  3. Do
    control-openhands browser click 'role=list >> has-text=Tarball >> role=button[name="Edit"]'
  4. Check
    control-openhands browser snapshot 'role=form'
  5. Note
    : form Edit automation with textbox Name (QA_F23 Pong), textbox Prompt and Edits apply to future runs only., combobox Agent profile (Default), combobox LLM profile (Active profile), spinbutton Timeout (seconds) Optional (600) with edit-automation-timeout-hint (Maximum time a single run may take. Leave empty for the default of 600 seconds (10 minutes); maximum 1800 seconds (30 minutes).), combobox Frequency (Daily), textbox Time of day (09:00), Cancel and Save.
  6. Note
    Each of these closes it (browser count 'testid=edit-automation-save' is 0):
  7. Do
    control-openhands browser click 'testid=edit-automation-cancel'
  8. Do
    control-openhands browser click 'role=form[name="Edit automation"] >> xpath=.. >> role=button[name="Cancel"] >> nth=0'

    (the X) and

  9. Do
    control-openhands browser mouse-click 1400 950

    (backdrop).

  10. Note
    Expected for Escape too, but today
  11. Do
    control-openhands browser press Escape
  12. Note
    leaves the count at 1 (see Gotchas).
  13. Expect
    After changing the timeout and cancelling, reopening shows 600 again.

Validation #

  1. Note
    In the dialog run
  2. Do
    control-openhands browser fill 'testid=edit-automation-name' ''
  3. Do
    control-openhands browser click 'testid=edit-automation-save'
  4. Check
    control-openhands browser snapshot 'role=form' --max-lines 6

    shows textbox "Name Name is required" [invalid] and alert: Name is required.

  5. Note
    Refill the name, then fill testid=edit-automation-timeout with 1.5, 0 and 1801 in turn, clicking Save after each;
  6. Check
    control-openhands browser text 'testid=edit-automation-timeout >> xpath=ancestor::label[1]/..'
  7. Note
    contains Timeout must be a valid number, Timeout must be positive and Timeout cannot exceed 1800 seconds respectively, and the dialog stays open.
  8. Note
    Cancel.

No-change Save #

  1. Note
    Reopen Edit,
  2. Check
    control-openhands browser network --clear
  3. Note
    click testid=edit-automation-save, then
  4. Check
    control-openhands browser network --last 20
  5. Note
    : the dialog is closed and no PATCH was sent; browser toasts is empty.

Weekly schedule, model and timeout #

  1. Note
    Reopen Edit and run
  2. Do
    control-openhands browser click 'testid=edit-automation-frequency'
  3. Check
    control-openhands browser snapshot 'role=listbox'

    (Daily [selected], Weekdays (Mon–Fri), Weekly),

  4. Do
    control-openhands browser click 'role=option[name="Weekly"]'
  5. Do
    control-openhands browser click 'testid=edit-automation-weekday'
  6. Do
    control-openhands browser click 'role=option[name="Wednesday"]'
  7. Do
    control-openhands browser fill 'testid=edit-automation-time' 10:30
  8. Do
    control-openhands browser click 'testid=edit-automation-model'
  9. Do
    control-openhands browser click 'role=option[name="deepseek-pro"]'
  10. Do
    control-openhands browser fill 'testid=edit-automation-timeout' 300
  11. Do
    control-openhands browser screenshot --feature F23.edit-schedule --name weekly-form
  12. Check
    control-openhands browser network --clear
  13. Do
    control-openhands browser click 'testid=edit-automation-save' --observe 'testid=edit-automation-save' --observe-ms 1500

    (Save → Saving... → absent) and

  14. Wait
    control-openhands browser wait-text 'Automation updated' --timeout 5000
  15. Note
    browser network --last 20 shows one PATCH /api/automation/v1/<id>.
  16. Note
    After
  17. Do
    control-openhands browser reload
  18. Check
    control-openhands browser text 'role=heading[name="Configuration"] >> xpath=ancestor::div[2]'

    reads Schedule 30 10 * * 3 and LLM profile deepseek-pro;

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

    has timeout 300 and enabled false.

  20. Note
    Reopening Edit shows Weekly, Wednesday, 10:30 (browser value 'testid=edit-automation-weekday' etc.).

Cleared time #

  1. Note
    Reopen Edit,
  2. Do
    control-openhands browser fill 'testid=edit-automation-time' ''
  3. Check
    control-openhands browser network --clear
  4. Note
    click Save.
  5. Note
    Expected: an inline message and the dialog stays open.
  6. Note
    Today the dialog closes as if saved, browser network --last 20 shows no PATCH and the schedule stays 30 10 * * 3.

Custom cron #

  1. Note
    Open QA_F23 Fail and Edit.
  2. Check
    control-openhands browser value 'testid=edit-automation-frequency'

    is Custom and

  3. Check
    control-openhands browser enabled 'testid=edit-automation-frequency'
  4. Note
    and ... 'testid=edit-automation-time' are false; testid=edit-automation-cron holds 0 0 1 1 * (placeholder */10 * * * *).
  5. Note
    Fill the cron with not a cron, click Save, and
  6. Check
    control-openhands browser text 'testid=edit-automation-cron >> xpath=ancestor::label[1]'
  7. Note
    adds Enter a valid cron expression; with 0 0 31 2 * it reads This cron expression will never run.
  8. Note
    Fill 0 0 2 1 * and testid=edit-automation-timeout with 1 (this makes the next run fail), Save, browser wait-text 'Automation updated'; after a reload the Configuration shows 0 0 2 1 *.

Failed run, its row and Debug with OpenHands #

  1. Note
    On QA_F23 Fail (timeout 1 from the previous bullet) click role=button[name="Run now"] and
  2. Wait
    control-openhands browser wait-text 'Automation dispatched'
  3. Expect
    The run stays Running for about 3-4 minutes and ends FAILED (poll control-openhands api GET /api/automation/v1/<id>/runs; drive the event automation bullets meanwhile).
  4. Do
    control-openhands browser goto /automations
  5. Note
    open QA_F23 Fail,
  6. Wait
    control-openhands browser wait 'testid=automation-activity-log >> testid=run-status-icon-failed'
  7. Check
    control-openhands browser text 'testid=automation-activity-log'
  8. Note
    : the newest row reads <time>, Timed out: command timed out or was killed, Failed.
  9. Note
    Expected beside the time: No Conversation (this is a prompt automation); today it reads No conversation — this run executed a script. Use View logs for its output. Run
  10. Do
    control-openhands browser click 'testid=automation-activity-log >> title=View logs >> nth=0'
  11. Check
    control-openhands browser snapshot 'role=dialog'
  12. Note
    : Run FAILED, Task No task outcome reported., System Error: Timed out: command timed out or was killed and Status detail: ..., the Output tab with the [setup] Fetching SDK version ... lines, and button Debug with OpenHands.
  13. Note
    Screenshot with --feature F23.debug-with-openhands --name failed-logs.
  14. Check
    control-openhands browser click 'testid=debug-automation-button' --expect-url '/conversations/'
  15. Note
    opens a new conversation;
  16. Check
    control-openhands conversation events <conversation id from browser url> --kinds MessageEvent
  17. Note
    starts The scheduled automation "QA_F23 Fail" failed during a run. Please investigate the error and fix the root cause., and
  18. Do
    control-openhands browser eval "document.body.innerText.includes('Timed out: command timed out or was killed')"

    is true (the page also shows the run id).

  19. Expect
    The agent then works on that prompt with the active profile.

Task outcome badge #

  1. Note
    Arrange (API, arrange only)
  2. Arrange
    control-openhands api POST /api/automation/v1/preset/prompt --write --data '{"name":"QA_F23 Blocked","prompt":"Do not run any tools. Call the finish tool right away with status \"blocked\" and outcome_summary \"QA blocked on purpose\".","trigger":{"type":"cron","schedule":"0 0 1 1 *","timezone":"UTC"},"enabled":true}'

    (a good thing to do while the Fail run is still running).

  3. Note
    Open QA_F23 Blocked, click role=button[name="Run now"], and poll
  4. Check
    control-openhands browser text 'testid=automation-activity-log'
  5. Note
    until it leaves Running (about 35 s): <time>, QA blocked on purpose, a cost such as $0.0016, Blocked;
  6. Check
    control-openhands browser testids 'testid=automation-activity-log'

    lists run-status-icon-warning (not -completed) although api GET /api/automation/v1/<id>/runs has status COMPLETED and run_metadata.finish_tool_response.status blocked.

  7. Do
    control-openhands browser click 'testid=automation-activity-log >> title=View logs'
  8. Note
    and browser snapshot 'role=dialog': Run COMPLETED, Task Blocked / QA blocked on purpose / Task metadata { "message": ... }.
  9. Note
    Screenshot with --feature F23.run-task-outcome --name blocked, Escape. partial_success (Partial, run-status-icon-warning) and unknown (Needs review, run-status-icon-needs-review) follow the same path with the same prompt shape.
  10. Expect
    The normal automation 1.19.0 prompt/plugin presets configure SDK 1.53.0 FinishTool with the required TaskOutcome.status, limited to success, partial_success, blocked, failed and unknown; SDK custom response schemas are a separate producer boundary.
  11. Expect
    Its normal prompt path therefore does not establish the new fallback for a finish response without a string status.
  12. Note
    That positive boundary needs a genuine completed run from a legacy or custom-schema producer whose real finish arguments have that shape; a run with no finish metadata checks only the ordinary Completed fallback.
  13. Note
    Arbitrary unrecognised strings and summary retention on a failed run need the corresponding genuine producer too.
  14. Note
    Until available, record those checks as blocked and do not treat injected run metadata or mocked LLM output as live proof (#17682).

Event automation #

  1. Note
    Open QA_F23 Event.
  2. Check
    control-openhands browser bbox 'testid=automation-prompt-content'

    is 240 px high, testid=automation-prompt-fade is present and

  3. Check
    control-openhands browser text 'testid=automation-prompt-toggle'

    is View More; screenshot with --feature F23.prompt-collapse --name collapsed.

  4. Note
    Click the toggle: height 360, no fade, text View Less; click again to collapse.
  5. Check
    control-openhands browser text 'role=heading[name="Configuration"] >> xpath=ancestor::div[2]'

    reads Repositories qa-example/qa-repo main, Trigger Event, Event Source github, Event Type pull_request.opened, Filter pull_request.draft == `false` && pull_request.base.ref == re…, Show more, LLM profile Active profile.

  6. Do
    control-openhands browser click 'role=button[name="Show more"]'

    shows the full filter and

  7. Check
    control-openhands browser attr 'role=button[name="Show less"]' aria-expanded

    is true.

  8. Check
    control-openhands browser text 'testid=automation-repository'

    is qa-example/qa-repo / main (the fixture's https://github.com/qa-example/qa-repo as owner/repo, with its branch badge) and

  9. Check
    control-openhands browser text 'role=heading[name="Plugins"] >> xpath=ancestor::div[2]'

    is Plugins / github:qa-example/qa-plugin; screenshot with --feature F23.plugins-repo --name event-repo-plugins.

  10. Note
    Export keeps them:
  11. Do
    control-openhands browser click 'role=button[name="Automation actions"]'
  12. Do
    control-openhands browser click 'role=list >> has-text=Tarball >> role=button[name="Export"]'
  13. Check
    control-openhands browser downloads --last 1 --contains '"branch": "main"'

    (qa-f23-event.automation.json, contains true); --contains '"repository": "https://github.com/qa-example/qa-repo"' and --contains 'github:qa-example/qa-plugin' are true too (the --inspect head ends inside the long prompt).

Event trigger in Edit #

  1. Note
    On QA_F23 Event open Edit:
  2. Check
    control-openhands browser count 'testid=edit-automation-frequency'

    is 0 and

  3. Check
    control-openhands browser text 'role=form[name="Edit automation"] >> text=Event Source >> xpath=ancestor::div[2]'

    reads Trigger Event / Event Source github / Event Type pull_request.opened / Filter <full filter>.

  4. Note
    Screenshot with --feature F23.edit-event-readonly --name event, then Cancel.

Phone #

  1. Do
    control-openhands browser viewport phone
  2. Note
    open QA_F23 Pong,
  3. Check
    control-openhands browser bbox 'role=heading[name="QA_F23 Pong"] >> xpath=ancestor::div[3]'

    (insideViewport true, pageHorizontalOverflow false) and

  4. Do
    control-openhands browser screenshot --feature F23.phone --name detail

    (the name and Run now wrap).

  5. Note
    Open Edit, then
  6. Check
    control-openhands browser bbox 'role=form[name="Edit automation"] >> xpath=..'
  7. Check
    control-openhands browser bbox 'testid=edit-automation-save'
  8. Note
    Expected: both insideViewport true.
  9. Note
    Today the dialog is about 940 px high with y about -48, Save sits at y 831 (insideViewport false), the X at a negative y (about -30), and
  10. Do
    control-openhands browser scroll 'testid=edit-automation-save'
  11. Note
    cannot move it; screenshot with --feature F23.phone --name edit-weekly.
  12. Note
    Cancel and
  13. Do
    control-openhands browser viewport desktop

Run now and save errors #

  1. Note
    Arrange a stale page: open QA_F23 Fail, then
  2. Arrange
    control-openhands api DELETE /api/automation/v1/<Fail id> --write
  3. Check
    control-openhands browser toasts --history --clear

    (empties the toast history), click role=button[name="Run now"], then

  4. Check
    control-openhands browser toasts --history
  5. Check
    control-openhands browser screenshot --feature F23.run-now-error --name toasts
  6. Note
    : exactly one toast, Automation not found, and no Request failed with status code 404.
  7. Note
    Expected: the page then shows Automation not found:
  8. Wait
    control-openhands browser wait 'role=heading[name="QA_F23 Fail"]' --state detached --timeout 15000
  9. Note
    succeeds (the not-found state takes about 7 s, as in States) and
  10. Check
    control-openhands browser snapshot 'role=paragraph >> text=Automation not found >> xpath=..'

    lists link Back to Automations (do not use wait-text 'Automation not found' here: the toast has the same text).

  11. Note
    Once the heading is gone, run
  12. Check
    control-openhands browser toasts --history
  13. Note
    again: it still holds only Automation not found (a late second toast, Request failed with status code 404, would come from the page's own refetch of the deleted automation).
  14. Note
    Known failure: the wait times out and the page keeps heading QA_F23 Fail, Active and an enabled Run now (control-openhands browser enabled 'role=button[name="Run now"]' is true) until a reload (#18062).
  15. Expect
    The save error does not reuse that page (it is gone once #18062 is fixed): arrange a second fixture,
  16. Arrange
    control-openhands api POST /api/automation/v1/preset/prompt --write --data '{"name":"QA_F23 Stale","prompt":"Reply with the single word: pong.","trigger":{"type":"cron","schedule":"0 0 1 1 *","timezone":"UTC"},"enabled":false}'

    (body.id is <stale-id>), then open QA_F23 Stale (control-openhands browser goto /automations and the card heading, as in Preconditions),

  17. Do
    control-openhands browser click 'role=button[name="Automation actions"]'
  18. Do
    control-openhands browser click 'role=list >> has-text=Tarball >> role=button[name="Edit"]'
  19. Note
    With the dialog open,
  20. Arrange
    control-openhands api DELETE /api/automation/v1/<stale-id> --write
  21. Check
    control-openhands browser toasts --history --clear
  22. Note
    Fill testid=edit-automation-name with QA_F23 Stale renamed and click testid=edit-automation-save: the dialog stays open (browser count 'testid=edit-automation-save' 1, browser value 'testid=edit-automation-name' still QA_F23 Stale renamed), and
  23. Check
    control-openhands browser toasts --history
  24. Note
    holds exactly one toast, Automation not found; screenshot with --feature F23.edit-save-error --name toast.
  25. Note
    Close it with
  26. Do
    control-openhands browser click 'testid=edit-automation-cancel'
  27. Note
    browser errors --app-only lists the induced POST /api/automation/v1/<Fail id>/dispatch and PATCH /api/automation/v1/<stale-id> 404s; both fixtures are already deleted.

Load more runs #

  1. Note
    Not run: it needs more than 20 runs of one automation (about 35 s and one model call each).
  2. Note
    With them,
  3. Do
    control-openhands browser click 'testid=automation-activity-log >> role=button[name="Load more"]'
  4. Note
    grows the list from 20 to 40 rows.

Script automation #

  1. Note
    Arrange a script automation through F22's bundle entry (UI arrange; F22 owns the creation proof):
  2. Do
    control-openhands browser goto /automations/templates
  3. Check
    control-openhands browser click 'testid=recommended-automation-card-news-digest' --expect-url '/automations/new/news-digest'
  4. Do
    control-openhands browser fill 'testid=setup-field-schedule' '0 0 1 1 *'
  5. Do
    control-openhands browser click 'testid=setup-continue-button'
  6. Wait
    control-openhands browser wait 'testid=setup-review' --timeout 10000
  7. Check
    control-openhands browser click 'testid=setup-continue-button' --expect-url '/automations/[0-9a-f-]{8,}' --timeout 60000
  8. Expect
    The detail page of Daily news digest shows role=heading[name="Script"] and no Prompt (browser count 'role=heading[name="Prompt"]' is 0);
  9. Check
    control-openhands browser text 'testid=automation-script-entrypoint'

    is python3 main.py, and browser eval "Array.from(document.querySelectorAll('[data-testid=automation-script-file]')).map(e=>e.innerText.split('\n').slice(0,2).join(' | '))" lists main.py | Entrypoint first, then config.json (both with line-numbered source).

  10. Note
    Screenshot with --feature F23.script-section --name news-digest. automation-script-unavailable (bundle unreadable) was not reached.
  11. Note
    Do not Run now: the script fetches public feeds and starts a model-backed conversation.
  12. Note
    Delete it in the Delete bullet.

Delete #

  1. Note
    Open QA_F23 Event, click the kebab and role=list >> has-text=Tarball >> role=button[name="Delete"].
  2. Check
    control-openhands browser snapshot 'role=dialog[name="Delete automation"]'

    shows Delete "QA_F23 Event"? This cannot be undone., Cancel and Delete (the same dialog as F21.delete).

  3. Do
    control-openhands browser click 'role=dialog[name="Delete automation"] >> role=button[name="Cancel"]'
  4. Note
    keeps the page (control-openhands browser count 'role=dialog[name="Delete automation"]' is 0).
  5. Note
    Reopen and
  6. Do
    control-openhands browser click 'role=dialog[name="Delete automation"] >> role=button[name="Delete"]' --expect-url '/automations(\?|$)'
  7. Check
    control-openhands api GET /api/automation/v1
  8. Note
    no longer lists it.
  9. Note
    Delete QA_F23 Pong, QA_F23 Blocked and Daily news digest the same way (and QA_F23 Fail if the error bullet was skipped); api GET /api/automation/v1 shows "total": 0.

Errors #

  1. Expect
    After each group
  2. Check
    control-openhands browser errors --app-only

    shows no page errors; the only app errors are the induced ones (404s for the unknown id and the deleted fixture, 502s while the service is stopped).

Gotchas and known limits

  • The Logs dialog's panel should keep its content reachable at every width (F23.run-logs-modal). Known failure (reproduced 2026-10-08): with a long task summary (the dialog drops the outcome_summary line breaks, so ask for about 1,500 characters, for example 12 lines of about 120 characters), the panel (max-h-[80vh]) does not scroll, and at phone the Output/Error tabs and the Output box sit below the viewport (browser bbox 'role=dialog >> role=tab[name="Error"]' insideViewport false, and browser scroll cannot reach it). At desktop that run still fits; about twice as much text pushes the Output box up to about 200 px below the panel. A short run fits at desktop; at phone even a ~200-character summary with metadata leaves the Output box about 15 px below the panel (#18173). browser bbox 'role=dialog' measures the full-screen wrapper, not the panel: measure the panel with browser eval.
  • The detail page has no main landmark: browser snapshot 'role=main' times out. Snapshot the page, or scope by section headings (role=heading[name="Configuration"] >> xpath=ancestor::div[2]).
  • The kebab menu has no test ids; role=list >> has-text=Tarball scopes the detail kebab (the dashboard kebab has no Tarball, so F21 uses has-text=Export).
  • Both the X and the footer button of the Edit dialog are named Cancel; use testid=edit-automation-cancel for the footer and role=form[name="Edit automation"] >> xpath=.. >> role=button[name="Cancel"] >> nth=0 for the X. The dialog has no role=dialog.
  • Known defect: Escape does not close the Edit dialog (the handler sits on the non-focusable backdrop div) (#17563). The delete confirmation, by contrast, is a role=dialog that Escape closes.
  • Known defect: at phone height the Edit dialog has no max-height or scrolling, so its title, X and Save are clipped and unreachable by scrolling (#17563).
  • Known defect: clearing Time of day on a preset schedule is silently ignored (#17948); Save closes the dialog with no request and no message.
  • On a local backend the Logs dialog reads every run's output from the server-level /api/bash/bash_events/search, with or without a conversation. It caches what it fetched: reload before checking its requests with browser network.
  • Known defect: prompt runs also have a bash_command_id, so every row shows the logs icon, and a prompt run that ended without a conversation is labelled as a script run (F23.run-no-conversation) (#17948).
  • The automation service (1.19.0 here) keeps repos and plugins only under preset_metadata; the UI reads them from there. Repositories show owner/repo without the host (a GitLab URL reads like a GitHub one), a repo without a ref gets no badge, and Export keeps only the first repository (all plugins). The Notification field reads a top-level notification that the service has no field for, so it never renders on a local stack (#16644 tracks removing or mapping it).
  • The Schedule field shows the raw cron (0 9 * * *) and no timezone: the service returns no schedule_human and no top-level timezone, so the Edit dialog also omits its Timezone: hint.
  • run-phase never appeared in the log here (phase_code stays null on the local service), so the phase chip is not reachable on this stack.
  • An LLM profile that does not exist (model: "qa-no-such-profile") does not fail the run: it ran and succeeded with the active profile. Use timeout 1 to get a failed run; it takes several minutes to fail.
  • Load more in the log (F23.activity-log-more) needs more than 20 runs of one automation; at about 35 s and one model call each it is left out of a normal pass (not-run).
  • The script section needs a tarball automation. The account-free way to get one is F22's Daily news digest bundle entry (it uploads its bundle and creates the automation); the custom-automation Upload tarball action needs a file you pack yourself. Its Edit dialog still shows an empty Prompt field next to the cron, although a script automation has no prompt.
  • Starting… on Run now lasts only for the dispatch request (under 50 ms locally); --observe does not catch it.
  • The run row is a plain <a href>, so opening a run's conversation reloads the whole app (a document request in browser network), unlike sidebar navigation.
  • A 404 detail request is retried three times before Automation not found shows (about 7 s of skeleton) (#17948); wait with wait-text, not count.
  • browser fill on the Timeout spinbutton with letters fails in Playwright and, after its 30 s retry, the dialog was found closed; use numeric values (1.5, 0, 1801) to reach the timeout errors.
  • The Tarball download is named <name>.tar (spaces become _) but is gzip-compressed (#17948); browser downloads --inspect does not list tar entries, so inspect it with tar -tzf.
  • Debug with OpenHands starts a real agent with the openhands-automation skill and the automation API key. It acts on live automations: here it looked up QA_F23 Fail by name (a newer fixture with the same name), PATCHed its timeout back to 600 and dispatched it, which adds runs and conversations. Stop it once it has started: on its page control-openhands browser click 'testid=stop-button', then control-openhands conversation wait <id> --until paused,finished,idle --timeout 45 (paused).
  • A successful run leaves a conversation <name> — <UTC timestamp> that stays after the automation is deleted (conversations family owns deleting it).

Source paths: src/routes/automation-detail.tsx, src/components/features/automations/detail/, src/components/features/automations/delete-confirmation-modal.tsx, src/hooks/query/use-automation-detail.ts, src/utils/automation-preset-sources.ts, src/hooks/query/use-bash-command-logs.ts, src/hooks/query/use-automation-tarball-files.ts, src/utils/automation-schedule.ts, src/utils/automation-timeout.ts, src/utils/automation-disabled-reason.ts, src/utils/automation-activity-log-export.ts.