Automation Git Sync
F24 · 26 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.
F24 Automation Git Sync
26 behaviors · 26 scripts & contracts · Maintained source
Entry points & prerequisites
What this family covers
Git Sync keeps an organization's automation definitions in a git repository. From the Automate dashboard an admin opens a Git Sync page with a Sync Status card (enabled and encryption pills, repository, branch, path, last synced commit and time, interval, pending changes, Sync now with a live activity row) and a Configuration form (enable switch, interval, repository URL, branch, path, commit author, access token, encryption key). Saving first checks that the repository is reachable; Save and sync now saves and then runs a cycle that commits every changed automation under <path>/<slug>/ and pushes it.
How a user gets here
- Sidebar Automate (
sidebar-automations-link), then the Git Sync button in the dashboard header (automations-git-sync). Only users who can manage automations see it (always, locally). - Direct URL
/automations/git-sync. - No command-menu entry, keyboard shortcut or Automate sub-navigation tab leads here; the page shows no Dashboard/Templates tabs (see Gotchas).
- Leave with Back to Automations (a link, not a button) at the top of the page.
Given
Preconditions:
- Baseline state (fresh
launch --new, doctored,onboard --skip). No LLM profile is needed: sync cycles run git only. - Git Sync is unconfigured:
control-openhands api GET /api/automation/v1/git-sync/statusshows"repo_url": "","enabled": falseand"last_synced_commit": null. - A local repository to push to:
control-openhands fixture git-repo --name qa-sync-remote. Itspathoutput is<repo-path>below ($OH_VERIFY_RUN/workspace/qa-sync-remote). Sync to branchqa-sync, notmain: the fixture is a non-bare repo withmainchecked out, and git refuses pushes to a checked-out branch. Remote state is read with plaingit -C <repo-path> ...(read-only second view). - An inert automation for the pending-changes bullets, created right before Sync a changed automation:
control-openhands api POST /api/automation/v1/preset/prompt --write --data '{"name":"QA_F24 Sync","prompt":"QA git sync fixture; never runs.","trigger":{"type":"event","source":"qa-f24","on":"qa.f24.never"}}'. Itsidis<automation-id>; later bullets mark it changed withcontrol-openhands api PATCH /api/automation/v1/<automation-id> --write --data '{"enabled":false}'(ortrue), which is arrange, not proof. Sync a deletion deletes it again.
Open from the dashboardF24.open · F24.loadingMapped behavior
Scenario F24--open-from-the-dashboard · Permalink · Source
Given
Use F24’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
- DoOpen /automations.
- DoChoose “Automations git sync”.
- DoChoose “Back to Automations”.
- DoOpen /automations/git-sync.
Expect
the Git Sync button on the Automate dashboard and the direct URL open the page (h1 Git Sync); Back to Automations returns to /automations.
a skeleton (git-sync-skeleton) shows while health, permissions and status load.
Exact CLI recipe & bug checks
Run control-openhands browser goto /automations, then control-openhands browser click 'testid=automations-git-sync' --expect-url '/automations/git-sync(\?|$)' --observe 'testid=git-sync-skeleton' --observe-ms 2000. The observation lists the skeleton (state "") for a few tens of ms, then <absent>. control-openhands browser text 'h1' is Git Sync. Run control-openhands browser click 'role=link[name="Back to Automations"]' --expect-url '/automations(\?|$)'; the URL is /automations. Then control-openhands browser goto /automations/git-sync (direct URL) and control-openhands browser screenshot --feature F24.open --name page-desktop --full-page.
Unconfigured cardF24.overview-unconfiguredMapped behavior
Scenario F24--unconfigured-card · Permalink · Source
Given
Use F24’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
- DoRun
control-openhands browser text 'role=heading[name="Sync Status"] >> xpath=ancestor::div[2]'. It readsSync Status / Disabled / Not encrypted / Sync now / Repository / Not configured / Branch / main / Path / automations / Last synced commit / Never synced / Last synced / Never synced / Sync every (seconds) / Manual only / Pending changes / 0(newline-separated).control-openhands browser enabled 'testid=git-sync-now-button'isfalse.
Expect
with nothing configured the card reads Disabled, Not encrypted, Repository Not configured, Branch main, Path automations, Never synced twice, Manual only, Pending 0, and Sync now is disabled.
Exact CLI recipe & bug checks
Run control-openhands browser text 'role=heading[name="Sync Status"] >> xpath=ancestor::div[2]'. It reads Sync Status / Disabled / Not encrypted / Sync now / Repository / Not configured / Branch / main / Path / automations / Last synced commit / Never synced / Last synced / Never synced / Sync every (seconds) / Manual only / Pending changes / 0 (newline-separated). control-openhands browser enabled 'testid=git-sync-now-button' is false.
Dirty trackingF24.form-dirtyMapped behavior
Scenario F24--dirty-tracking · Permalink · Source
Given
Use F24’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 “300” in “Git sync interval”.
- DoEnter “0” in “Git sync interval”.
- DoChoose “Git sync enabled switch” / its containing control.
Expect
Save Changes and Save and sync now stay disabled until a field differs from the stored value (reverting disables them again); Save and sync now also needs Enable Git Sync on.
Exact CLI recipe & bug checks
control-openhands browser enabled 'testid=git-sync-save-button' and enabled 'testid=git-sync-save-and-sync-button' are both false. Run control-openhands browser fill 'testid=git-sync-interval-input' 300: Save is true, Save and sync stays false (sync is off). control-openhands browser fill 'testid=git-sync-interval-input' 0: Save is false again. Run control-openhands browser click 'testid=git-sync-enabled-switch >> xpath=ancestor::label'; control-openhands browser eval "document.querySelector('[data-testid=git-sync-enabled-switch]').checked" is true and both buttons are true. Click the label again; Save is false.
Unreachable repositoryF24.check-failureMapped behavior
Scenario F24--unreachable-repository · Permalink · Source
Given
Use F24’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 “/nonexistent/qa-missing.git” in “Git sync repo url”.
- DoChoose “Git sync save”.
- WaitWait for “Git sync check failure” to appear.
- WaitWait for the element identified in the CLI recipe to appear.
- DoReload the page.
Expect
a change to repository URL, branch or token is checked first (Checking repository...); an unreachable repo shows Could not reach the repository with these settings with git's output and the hint Fix the settings above, or press Save again to store them anyway., saves nothing, and a second Save stores the values anyway.
Exact CLI recipe & bug checks
Run control-openhands browser fill 'testid=git-sync-repo-url-input' /nonexistent/qa-missing.git and control-openhands browser click 'testid=git-sync-save-button' --observe 'testid=git-sync-save-button' --observe-ms 3000; the observation includes Checking repository.... Run control-openhands browser wait 'testid=git-sync-check-failure' --timeout 25000 and control-openhands browser text 'testid=git-sync-check-failure': Could not reach the repository with these settings, git's fatal: '/nonexistent/qa-missing.git' does not appear to be a git repository, then Fix the settings above, or press Save again to store them anyway. Take control-openhands browser screenshot 'testid=git-sync-check-failure' --feature F24.check-failure --name failure-block. control-openhands api GET /api/automation/v1/git-sync/status still has "repo_url": "". Click testid=git-sync-save-button again, then control-openhands browser wait-text 'Git Sync settings saved.' --timeout 10000; browser count 'testid=git-sync-check-failure' is 0. After control-openhands browser reload, control-openhands browser text 'testid=git-sync-enabled-pill' is Enabled (configuring a repo turns sync on) and the card shows /nonexistent/qa-missing.git as plain text (browser count 'testid=git-sync-repo-link' is 0).
Failing cycleF24.sync-failureMapped behavior
Scenario F24--failing-cycle · Permalink · Source
Given
Use F24’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 “Git sync now”.
- WaitWait for “Git sync error banner” to appear.
- DoReload the page.
Expect
a failing cycle ends Sync failed and shows the Last sync error banner with git's message and its age; the banner survives a reload and disappears after the next successful cycle.
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=git-sync-now-button' --observe 'testid=git-sync-activity-row' --observe-ms 8000. The observation goes Syncing... / started 0s ago (counting up each second) and ends Sync failed; control-openhands browser attr 'testid=git-sync-activity-row' data-state is failed. Run control-openhands browser wait 'testid=git-sync-error-banner' --timeout 20000 and control-openhands browser text 'testid=git-sync-error-banner': Last sync error, git command failed (128): git clone --origin origin /nonexistent/qa-missing.git ., fatal: repository '/nonexistent/qa-missing.git' does not exist, Ns ago. Screenshot with control-openhands browser screenshot --feature F24.sync-failure --name failed. After control-openhands browser reload the banner count is 1 and the activity row count is 0 (the row is page-local).
Configure and Save and syncF24.save-and-syncMapped behavior
Scenario F24--configure-and-save-and-sync · Permalink · Source
Given
Use F24’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 “$OH_VERIFY_RUN/workspace/qa-sync-remote” in “Git sync repo url”.
- DoEnter “qa-sync” in “Git sync branch”.
- DoEnter “qa-automations” in “Git sync path”.
- DoChoose “Git sync save and sync”.
- WaitWait for the element identified in the CLI recipe to appear.
Expect
Save and sync now with a reachable repo saves (toast Git Sync settings saved.), then runs a cycle: the activity row goes Syncing... started Ns ago → Sync complete, and the card shows the short commit and Last synced Ns ago.
Exact CLI recipe & bug checks
Save and sync now stays disabled until Enable Git Sync is on (F24.check-failure turns it on; on a fresh stack turn it on first). Run control-openhands browser fill 'testid=git-sync-repo-url-input' "$OH_VERIFY_RUN/workspace/qa-sync-remote", control-openhands browser fill 'testid=git-sync-branch-input' qa-sync, control-openhands browser fill 'testid=git-sync-path-input' qa-automations, then control-openhands browser click 'testid=git-sync-save-and-sync-button' --observe 'testid=git-sync-activity-row' --observe-ms 10000. The observation goes Syncing... started 0s ago → Sync complete within a few seconds; browser count 'testid=git-sync-check-failure' and browser count 'testid=git-sync-error-banner' are 0 (a successful cycle clears the last error). The toast Git Sync settings saved. also fires, but it is gone before a 10 s observation ends; to see it, run control-openhands browser wait-text 'Git Sync settings saved.' --timeout 5000 right after the click instead of --observe. The card text (as in Unconfigured card) shows <repo-path>, qa-sync, qa-automations, a 7-character commit and Last synced / Ns ago. git -C <repo-path> log --oneline qa-sync shows the fixture's Initial fixture commit (the cycle created the branch).
Sync a changed automationF24.sync-nowKnown issue
Scenario F24--sync-a-changed-automation · Permalink · Source
Given
Use F24’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
- DoReload the page.
- DoChoose “Git sync now”.
Expect
a changed automation raises Pending changes (amber); Sync now shows Syncing... with 1 pending, ends Sync complete, drops Pending to 0 and pushes <path>/<slug>/automation.yaml to the branch.
Linked known issues
Known issue OpenHands/automation#586 (F24.sync-now): a YAML-only repository edit can replace an unchanged uploaded script bundle because canonical gzip bytes retain their creation second. Recheck a disposable Git Sync repository and script automation, change only YAML, arrange the canonical rebuilds across different seconds, and compare the real saved upload reference before and after sync. Equal decompressed contents or a serializer-only probe do not prove that Canvas reuses its upload.
Exact CLI recipe & bug checks
Create QA_F24 Sync (Preconditions), control-openhands browser reload, then control-openhands browser text 'text=Pending changes >> xpath=ancestor::div[1]/..' (Pending changes / 1) and control-openhands browser attr 'text=Pending changes >> xpath=ancestor::div[1]/.. >> span.text-warning' class (text-warning). Screenshot with control-openhands browser screenshot --feature F24.sync-now --name pending. Run control-openhands browser click 'testid=git-sync-now-button' --observe 'testid=git-sync-activity-row' --observe-ms 8000: Syncing... / started 0s ago / 1 pending, then control-openhands browser text 'testid=git-sync-activity-row' is Sync complete and Pending changes reads 0. git -C <repo-path> log --oneline qa-sync has a new Sync automations from agent server commit whose short hash matches the card's Last synced commit, and git -C <repo-path> ls-tree -r --name-only qa-sync lists qa-automations/qa-f24-sync/automation.yaml and qa-automations/qa-f24-sync/tarball/....
Commit authorF24.authorMapped behavior
Scenario F24--commit-author · Permalink · Source
Given
Use F24’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 sync author email”.
- DoChoose “Git sync save”.
- WaitWait for the element identified in the CLI recipe to appear.
- DoReload the page.
- DoReload the page.
- WaitWait for the element identified in the CLI recipe to appear.
Expect
commit author name and email are optional; an invalid email is refused by the browser before any request; saved values are used as the author of the next sync commit (the fields reload blank).
Exact CLI recipe & bug checks
Run control-openhands browser fill 'testid=git-sync-author-email-input' not-an-email, control-openhands browser network --clear, control-openhands browser click 'testid=git-sync-save-button', then control-openhands browser eval "document.querySelector('[data-testid=git-sync-author-email-input]').validationMessage" (Please include an '@' in the email address. 'not-an-email' is missing an '@'.); control-openhands browser network lists no git-sync/config request. Fill testid=git-sync-author-name-input with 'QA F24 Bot' and testid=git-sync-author-email-input with qa-f24@example.com, click Save, control-openhands browser wait-text 'Git Sync settings saved.' --timeout 10000, control-openhands browser reload; control-openhands browser value 'testid=git-sync-author-name-input' is "" (the fields never show the stored author). Mark the automation changed (PATCH {"enabled":false}), control-openhands browser reload, click testid=git-sync-now-button, control-openhands browser wait '[data-testid=git-sync-activity-row]:not([data-state=running])' --timeout 30000; then git -C <repo-path> log -1 --format='%an <%ae>' qa-sync is QA F24 Bot <qa-f24@example.com>.
Encryption keyF24.encryptionMapped behavior
Scenario F24--encryption-key · Permalink · Source
Given
Use F24’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.
- DoReload the page.
- WaitWait for the element identified in the CLI recipe to appear.
- SetupUse the phone viewport.
- SetupUse the desktop viewport.
Expect
saving an encryption key turns the pill to Encrypted and the placeholder to An encryption key is currently set; automations exported afterwards are ciphertext; Clear existing encryption key disables the input and saving it returns to Not encrypted.
Exact CLI recipe & bug checks
Start with plaintext exported by the preceding sync bullets. Run control-openhands browser attr 'testid=git-sync-encryption-key-input' placeholder (No encryption key set) and record git -C <repo-path> rev-parse qa-sync. Fill the field with qa-f24-dummy-key, click testid=git-sync-save-button, immediately control-openhands browser wait-text 'Git Sync settings saved.' --timeout 10000, then control-openhands browser reload. The pill is Encrypted, the placeholder is An encryption key is currently set, and the password value is empty. control-openhands api GET /api/automation/v1/git-sync/status shows dirty_count 1. Without changing the automation, click testid=git-sync-now-button, wait with control-openhands browser wait '[data-testid=git-sync-activity-row]:not([data-state=running])' --timeout 30000, then read the head and git -C <repo-path> show qa-sync:qa-automations/qa-f24-sync/automation.yaml | head -c 20: the head is new and the file begins gAAAAA. Separately verify dirty export: PATCH the fixture enabled state, reload and sync as above; the file remains ciphertext. This PATCH does not prove the key-only contract. Record that encrypted head and ciphertext, fill the key with qa-f24-different-dummy-key, Save, wait for the toast and reload. The status has dirty_count 1; Sync now without an automation PATCH produces another head and different ciphertext. Record the encrypted head, fill the key with any dummy text and click testid=git-sync-clear-encryption-key-switch >> xpath=ancestor::label: the field becomes disabled and empty. Save, wait for the toast and reload: Not encrypted, placeholder No encryption key set, and the clear switch is unchecked. Status again has dirty_count 1; Sync now without an automation change produces a new head and plaintext. Repeat key-only set, rotate and clear at control-openhands browser viewport phone, starting from the plaintext left by desktop clear, and record both viewport entries with selected backend Local. Return with control-openhands browser viewport desktop.
Access tokenF24.tokenMapped behavior
Scenario F24--access-token · Permalink · Source
Given
Use F24’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 “Git sync clear token switch” / its containing control.
Expect
the access token is a password field that is never shown again; changing it runs the reachability check; Clear existing token disables the input and saves a cleared token.
Exact CLI recipe & bug checks
control-openhands browser attr 'testid=git-sync-token-input' type is password and the placeholder Leave blank to keep the current token. Fill it with qa-dummy-token, run control-openhands browser network --clear, click Save with --observe 'testid=git-sync-save-button' --observe-ms 2000 (shows Checking repository...), wait for the toast; control-openhands browser network --last 6 lists /api/automation/v1/git-sync/check then /api/automation/v1/git-sync/config. After reload the token input is "". Run control-openhands browser click 'testid=git-sync-clear-token-switch >> xpath=ancestor::label'; browser enabled 'testid=git-sync-token-input' is false. Click Save, wait for the toast, reload; browser eval "document.querySelector('[data-testid=git-sync-clear-token-switch]').checked" is false.
IntervalF24.intervalMapped behavior
Scenario F24--interval · Permalink · Source
Given
Use F24’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
- DoFill
testid=git-sync-interval-inputwith300, Save, wait for the toast, reload: the card'sSync every (seconds)field readsEvery 300s(control-openhands browser text 'role=heading[name="Sync Status"] >> xpath=ancestor::div[2]') and the input value is300. Fill it with'', Save, reload:Manual onlyand input0. Fill-5and click Save:browser eval "document.querySelector('[data-testid=git-sync-interval-input]').validationMessage"isValue must be greater than or equal to 0.; reload to drop the draft.
Expect
Sync every (seconds) saves N as Every Ns, blank or 0 as Manual only, and refuses negative numbers.
Exact CLI recipe & bug checks
Fill testid=git-sync-interval-input with 300, Save, wait for the toast, reload: the card's Sync every (seconds) field reads Every 300s (control-openhands browser text 'role=heading[name="Sync Status"] >> xpath=ancestor::div[2]') and the input value is 300. Fill it with '', Save, reload: Manual only and input 0. Fill -5 and click Save: browser eval "document.querySelector('[data-testid=git-sync-interval-input]').validationMessage" is Value must be greater than or equal to 0.; reload to drop the draft.
Pause and resumeF24.pauseMapped behavior
Scenario F24--pause-and-resume · Permalink · Source
Given
Use F24’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
- DoClick
testid=git-sync-enabled-switch >> xpath=ancestor::label;browser enabled 'testid=git-sync-save-and-sync-button'isfalse. Runcontrol-openhands browser network --clear, click Save, wait for the toast;control-openhands browser networklists nogit-sync/check. After reload the pill isDisabled,enabled 'testid=git-sync-now-button'isfalseand the card still shows<repo-path>; screenshot--feature F24.pause --name disabled. The switch's help text (control-openhands browser text 'testid=git-sync-enabled-switch >> xpath=ancestor::label/following-sibling::p') isSync is on as soon as a repository is configured. Turn this off to pause syncing without losing the configuration.Click the switch label again, Save, wait for the toast: the pill isEnabled.
Expect
turning Enable Git Sync off and saving (no reachability check) shows Disabled, disables Sync now and keeps the configuration; turning it back on restores Enabled.
Exact CLI recipe & bug checks
Click testid=git-sync-enabled-switch >> xpath=ancestor::label; browser enabled 'testid=git-sync-save-and-sync-button' is false. Run control-openhands browser network --clear, click Save, wait for the toast; control-openhands browser network lists no git-sync/check. After reload the pill is Disabled, enabled 'testid=git-sync-now-button' is false and the card still shows <repo-path>; screenshot --feature F24.pause --name disabled. The switch's help text (control-openhands browser text 'testid=git-sync-enabled-switch >> xpath=ancestor::label/following-sibling::p') is Sync is on as soon as a repository is configured. Turn this off to pause syncing without losing the configuration. Click the switch label again, Save, wait for the toast: the pill is Enabled.
Trigger while disabled elsewhereF24.sync-disabled-errorMapped behavior
Scenario F24--trigger-while-disabled-elsewhere · Permalink · Source
Given
Use F24’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.
- DoChoose “Git sync now”.
- WaitWait for the element identified in the CLI recipe to appear.
- WaitWait for “Git sync enabled pill” / containing “Disabled” to appear.
Expect
Sync now on a page that still shows Enabled after sync was turned off elsewhere shows the toast Enable Git Sync before triggering a sync.; the pill catches up on the next idle poll (≤ 15 s).
Exact CLI recipe & bug checks
With the page showing Enabled, arrange control-openhands api PUT /api/automation/v1/git-sync/config --write --data '{"enabled":false}' (another admin pausing), then immediately control-openhands browser click 'testid=git-sync-now-button' and control-openhands browser wait-text 'Enable Git Sync before triggering a sync.' --timeout 5000. The pill still says Enabled at first; control-openhands browser wait 'testid=git-sync-enabled-pill >> has-text=Disabled' --timeout 20000 succeeds within about 15 s. browser errors --app-only lists the induced 503 on /api/automation/v1/git-sync/sync (expected). Turn sync back on through the form (switch label, Save).
Repository linkF24.repo-linkMapped behavior
Scenario F24--repository-link · Permalink · Source
Given
Use F24’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 “Git sync check failure” to appear.
Expect
an http(s) repository is shown as a link (new tab) to the browsable URL without .git and without embedded credentials, and the link text drops the credentials too; a local path stays plain text.
Exact CLI recipe & bug checks
Fill testid=git-sync-repo-url-input with https://qa-user:qa-pass@git.example.invalid/qa-org/qa-repo.git, click Save, browser wait 'testid=git-sync-check-failure' --timeout 30000 (git's output redacts the URL as https://***@git.example.invalid/...; the failure itself is CONNECT tunnel failed, response 502 behind the sandbox proxy, a resolve error elsewhere), click Save again, wait for the toast, reload. control-openhands browser attr 'testid=git-sync-repo-link' href is https://git.example.invalid/qa-org/qa-repo and attr ... target is _blank. control-openhands browser text 'testid=git-sync-repo-link' is https://git.example.invalid/qa-org/qa-repo.git: the link text drops qa-user:qa-pass@ and keeps .git. Screenshot with control-openhands browser screenshot 'role=heading[name="Sync Status"] >> xpath=ancestor::div[2]' --feature F24.repo-link --name https-link. Restore: fill the URL with "$OH_VERIFY_RUN/workspace/qa-sync-remote", Save, wait for the toast; browser count 'testid=git-sync-repo-link' is 0 (a local path is plain text).
Interval cycleF24.background-syncMapped behavior
Scenario F24--interval-cycle · Permalink · Source
Given
Use F24’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 “"<old-hash>"” to disappear.
Expect
with an interval set, the backend runs cycles on its own and the open page picks up the new commit on its idle poll, without a reload.
Exact CLI recipe & bug checks
Read the current short commit from the card (control-openhands browser text 'text=Last synced commit >> xpath=ancestor::div[1]/..', <old-hash>). Fill testid=git-sync-interval-input with 20, Save, wait for the toast; the card reads Every 20s. Mark the automation changed (PATCH {"enabled":false}), do not reload, and run control-openhands browser wait 'text="<old-hash>"' --state detached --timeout 60000. It succeeds within about 35 s (interval plus the page's 15 s idle poll); the card shows a new hash and Last synced / Ns ago, matching git -C <repo-path> log --oneline -1 qa-sync. Set the interval back to 0 (fill, Save, toast) so later bullets are not raced by background cycles.
Sync a deletionF24.sync-deleteMapped behavior
Scenario F24--sync-a-deletion · Permalink · Source
Given
Use F24’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.
- DoReload the page.
- DoChoose “Git sync now”.
Expect
deleting a synced automation raises Pending changes; the next cycle commits the removal of its <path>/<slug>/ folder.
Exact CLI recipe & bug checks
Arrange the deletion (owned by F21): control-openhands api DELETE /api/automation/v1/<automation-id> --write (status 204). control-openhands browser reload; browser text 'text=Pending changes >> xpath=ancestor::div[1]/..' is Pending changes / 1. Run control-openhands browser click 'testid=git-sync-now-button' --observe 'testid=git-sync-activity-row' --observe-ms 6000 (Syncing... / started 0s ago / 1 pending → Sync complete); Pending changes reads 0. git -C <repo-path> log --oneline -1 qa-sync is a new Sync automations from agent server commit matching the card's Last synced commit, and git -C <repo-path> ls-tree -r --name-only qa-sync lists only the fixture's README.md and src/... files: qa-automations/qa-f24-sync/ is gone.
Empty branch and pathF24.field-defaultsMapped behavior
Scenario F24--empty-branch-and-path · Permalink · Source
Given
Use F24’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 “” in “Git sync branch”.
- DoEnter “” in “Git sync path”.
- DoChoose “Git sync save”.
- WaitWait for the element identified in the CLI recipe to appear.
- DoReload the page.
Expect
emptying Branch or Path and saving clears the override instead of storing an empty value: the card and inputs fall back to main and automations.
Exact CLI recipe & bug checks
Run control-openhands browser fill 'testid=git-sync-branch-input' '' and browser fill 'testid=git-sync-path-input' '', then browser click 'testid=git-sync-save-button' --observe 'testid=git-sync-save-button' --observe-ms 2000 (Checking repository...: the branch changed; main exists in the fixture, so the check passes) and browser wait-text 'Git Sync settings saved.' --timeout 10000. After browser reload the card reads Branch main and Path automations, browser value 'testid=git-sync-branch-input' is main and browser value 'testid=git-sync-path-input' is automations; control-openhands api GET /api/automation/v1/git-sync/status has "branch": "main" and "path": "automations". Do not press Sync now here: main is the fixture's checked-out branch.
ssh remote linkF24.repo-link-sshMapped behavior
Scenario F24--ssh-remote-link · Permalink · Source
Given
Use F24’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 “Git sync check failure” to appear.
Expect
an scp-style ssh remote (git@host:org/repo.git) is also shown as a link, rebuilt over https (https://host/org/repo); the label keeps the configured value.
Exact CLI recipe & bug checks
Fill testid=git-sync-repo-url-input with git@github.com:qa-org/qa-repo.git, click Save, browser wait 'testid=git-sync-check-failure' --timeout 30000 (here git reports ssh: not found; with ssh installed it is an auth or host-key failure), click Save again, wait for the toast, reload. control-openhands browser attr 'testid=git-sync-repo-link' href is https://github.com/qa-org/qa-repo, attr ... target is _blank and browser text 'testid=git-sync-repo-link' is git@github.com:qa-org/qa-repo.git.
Clear the repositoryF24.clear-repoMapped behavior
Scenario F24--clear-the-repository · Permalink · Source
Given
Use F24’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 “Git sync check failure” to appear.
Expect
emptying the repository URL is checked (No repository URL is configured.), and a second Save clears it: the card returns to Not configured and Disabled.
Exact CLI recipe & bug checks
Fill testid=git-sync-repo-url-input with '', click Save, control-openhands browser wait 'testid=git-sync-check-failure' --timeout 25000; its text is Could not reach the repository with these settings / No repository URL is configured. / Fix the settings above, .... Click Save again, wait for the toast, reload: the card reads Disabled and Repository Not configured (branch, path and last commit stay), and the URL input is "" with its placeholder https://github.com/org/repo.git.
Phone layoutF24.phoneMapped behavior
Scenario F24--phone-layout · Permalink · Source
Given
Use F24’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.
- DoScroll “Git sync repo url” as specified in the recipe.
- SetupUse the desktop viewport.
Expect
the page fits a 390 px viewport without horizontal overflow; the card's fields stay in two columns.
Exact CLI recipe & bug checks
Run control-openhands browser viewport phone, control-openhands browser bbox 'role=heading[name="Sync Status"] >> xpath=ancestor::div[2]' (insideViewport true, pageHorizontalOverflow false) and control-openhands browser screenshot --feature F24.phone --name status: two columns of fields, long paths wrap. Then control-openhands browser scroll 'testid=git-sync-repo-url-input', control-openhands browser bbox 'testid=git-sync-save-and-sync-button' (pageHorizontalOverflow false) and control-openhands browser screenshot --feature F24.phone --name form. Return with control-openhands browser viewport desktop.
Automation service downF24.backend-downMapped behavior
Scenario F24--automation-service-down · Permalink · Source
Given
Use F24’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: stop automation.
- DoReload the page.
- WaitWait for the element identified in the CLI recipe to appear.
- DoChoose “Retry”.
- DoRestart the run-owned stack as specified.
- WaitWait for “Git sync enabled pill” to appear.
Expect
with the automation service down the page shows Automations Unavailable with Retry; Retry after the service is back shows the page with the stored configuration.
Exact CLI recipe & bug checks
Run control-openhands service stop automation, control-openhands browser reload and control-openhands browser wait-text 'Automations Unavailable' --timeout 40000; the page shows the heading, The automations backend is not available right now... and Retry, and no Back link. Screenshot with control-openhands browser screenshot --feature F24.backend-down --name unavailable. control-openhands browser network --clear, control-openhands browser click 'role=button[name="Retry"]'; control-openhands browser network shows a new /api/automation/health request and the panel stays. Run control-openhands restart, click Retry again and control-openhands browser wait 'testid=git-sync-enabled-pill' --timeout 15000: the page is back with the stored configuration. control-openhands doctor is ok.
Old backendF24.unsupportedMapped behavior
Scenario F24--old-backend · Permalink · Source
Given
Use F24’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
- DoFollow the onboarding setup in the recipe.
- DoOpen /automations/git-sync.
- WaitWait for the element identified in the CLI recipe to appear.
- DoChoose “Back to Automations”.
- DoStop the run-owned stack.
Expect
an automation backend without the Git Sync API (status 404) shows Git Sync is not available on this backend with Back to Automations; the dashboard still shows the Git Sync button there.
Exact CLI recipe & bug checks
Needs a second, fresh run on an automation release without the Git Sync API (1.7.1 is the last one; 1.8.0 added git_sync/router.py). Stop the main run first if memory is short, then export OH_VERIFY_RUN=$(OH_VERIFY_RUN= control-openhands launch --new --automation-ref 1.7.1 --print-run) (uvx installs the tag from GitHub), control-openhands doctor (ok) and control-openhands onboard --skip. control-openhands api GET /api/automation/v1/git-sync/status is 404. Run control-openhands browser goto /automations/git-sync and control-openhands browser wait-text 'Git Sync is not available on this backend' --timeout 20000; the page also reads The automation backend is running a version without the Git Sync API. Update it to a version that supports Git Sync. with Back to Automations below it. Screenshot with control-openhands browser screenshot --feature F24.unsupported --name unsupported. control-openhands browser click 'role=link[name="Back to Automations"]' --expect-url '/automations(\?|$)' returns to the dashboard, where browser count 'testid=automations-git-sync' is still 1. browser errors --app-only lists only the expected 404 on git-sync/status. control-openhands stop this run.
No permissionF24.no-accessBlocked
Scenario F24--no-permission · Permalink · Source
Given
Use F24’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: local backends always grant
manage_automations. Needs a Cloud backend signed in as an organization member (not admin or owner); expectedGit Sync is managed by organization admins/Only organization admins and owners can view and configure Git Sync.at/automations/git-sync, andcontrol-openhands browser count 'testid=automations-git-sync'0on/automations.
Expect
a user without manage_automations (Cloud member) sees Git Sync is managed by organization admins, and the dashboard hides the Git Sync button.
Exact CLI recipe & bug checks
Blocked: local backends always grant manage_automations. Needs a Cloud backend signed in as an organization member (not admin or owner); expected Git Sync is managed by organization admins / Only organization admins and owners can view and configure Git Sync. at /automations/git-sync, and control-openhands browser count 'testid=automations-git-sync' 0 on /automations.
Repository taken by another orgF24.conflictBlocked
Scenario F24--repository-taken-by-another-org · Permalink · Source
Given
Use F24’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: the local backend has one organization. Needs two Cloud orgs; saving the same URL, branch and path in the second shows the error toast
Another organization already syncs this repository, branch and path. Sharing them would import each other's automations; use a different repository or path.and keeps the form dirty for a retry.
Expect
saving a repository, branch and path that another organization already syncs is refused (409) with a toast naming the conflict.
Exact CLI recipe & bug checks
Blocked: the local backend has one organization. Needs two Cloud orgs; saving the same URL, branch and path in the second shows the error toast Another organization already syncs this repository, branch and path. Sharing them would import each other's automations; use a different repository or path. and keeps the form dirty for a retry.
Status errorF24.error-stateMapped behavior
Scenario F24--status-error · Permalink · Source
Given
Use F24’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
- DoNot driven: no non-mocked way makes
GET /api/automation/v1/git-sync/statusfail with a non-404 status while/api/automation/healthis ok. Expected: the generic automation error panel with Retry.
Expect
any other status failure shows the generic automation error panel with Retry.
Exact CLI recipe & bug checks
Not driven: no non-mocked way makes GET /api/automation/v1/git-sync/status fail with a non-404 status while /api/automation/health is ok. Expected: the generic automation error panel with Retry.
Clean upF24 · setup / cleanupSupport step
Scenario F24--clean-up · Permalink · Source
Given
Use F24’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
- DoThe fixture automation was deleted in Sync a deletion (
control-openhands api GET /api/automation/v1lists none) and Git Sync is cleared by Clear the repository; the fixture repo goes away with the run. Checkcontrol-openhands browser errors --app-onlyon the main run:pageErrorsis0, and only the induced503ongit-sync/syncfrom Trigger while disabled elsewhere and the502s on/api/automation/health,git-sync/status,telemetry/consentandsdk-versionwhile the service was stopped are expected.
Expect
Complete this support step before continuing to the next scenario.
Exact CLI recipe & bug checks
The fixture automation was deleted in Sync a deletion (control-openhands api GET /api/automation/v1 lists none) and Git Sync is cleared by Clear the repository; the fixture repo goes away with the run. Check control-openhands browser errors --app-only on the main run: pageErrors is 0, and only the induced 503 on git-sync/sync from Trigger while disabled elsewhere and the 502s on /api/automation/health, git-sync/status, telemetry/consent and sdk-version while the service was stopped are expected.
Family notes, prerequisites for known issues & limits Known issueBlocked
- Configuring a repository is what turns sync on: the backend's pause switch defaults to on, so the first save of a URL flips the pill to
Enabledeven though the switch was never touched, and clearing the URL turns it off again. The help text under the switch says so (Sync is on as soon as a repository is configured. ...). A pause is the exception: it survives clearing and re-entering the URL, so the pill staysDisableduntil the switch is turned back on. - A local path (or
file://URL) is a valid remote, which is what makes this family drivable without credentials. Use a branch other than the fixture's checked-outmain: git's defaultreceive.denyCurrentBranchrefuses pushes to a non-bare repo's checked-out branch (not driven here; every recipe usesqa-sync). - The reachability check runs only when repository URL, branch or token changed (
git ls-remote, 20 s timeout). Interval, path, author, encryption key and the enable switch save without it. A check that cannot run at all (old backend, network error) never blocks a save. - The second Save after a failed check saves the same values without checking again; editing any checked field re-arms the check.
- The activity row (
git-sync-activity-row,data-staterunning/succeeded/failed) is page state: it disappears on reload and does not appear for a background cycle that starts and ends between two idle polls (15 s). Assert background cycles through the commit hash, not the row. - Feedback colours are semantic tokens (#18346): the check-failure block, the Last sync error banner and a
Sync failedrow use the danger surface and feedback-error text (rgb(231, 106, 94)on OpenHands-Neutral,rgb(192, 40, 45)on white under Light+), andSync completeuses success (rgb(165, 231, 94)Neutral,rgb(15, 115, 77)Light+); verified 2026-10-13 on desktop. Read them withbrowser evalofgetComputedStyle(...).coloron the text elements insidegit-sync-check-failure,git-sync-error-bannerandgit-sync-activity-rowafter switching themes on/settings/app(F16.color-theme). - Toasts (
Git Sync settings saved.,Enable Git Sync before triggering a sync.) last a few seconds: read them withbrowser wait-textimmediately after the click, never after a long--observewindow. - Save's label cycles
Save Changes→Checking repository...(only when a checked field changed) →Saving...→Save Changesin about 100 ms against a local repo;--observe 'testid=git-sync-save-button'records it. - Setting, rotating or clearing the encryption key re-exports the automation even when its definition is unchanged. Each key-only save sets
dirty_countto1; the next sync creates a commit with ciphertext after set, different ciphertext after rotation and plaintext after clear. Read the dirty count and exported file before any automation PATCH; a separate dirty export tests a different contract. - Nothing on the page says whether a token is stored: the token placeholder is always
Leave blank to keep the current token, and the author fields always reload blank. Prove author changes from the commit (git log --format='%an <%ae>'). - The card's Repository value drops embedded credentials, as link text and as plain text: an http(s) URL loses its whole
user:token@(a token alone in the user slot too), other schemes keep the user and lose only the password (ssh://git@host:2222/...), and the rest stays as configured. The Repository URL input andGET /api/automation/v1/git-sync/statusstill carry the credentials, so a screenshot of the form shows them. Never type a real token into the URL. - The page has no Automate sub-navigation (Dashboard / Templates) and no command-menu entry; leave through Back to Automations (
role=link, not a button). - Deleted automations stay in Pending changes until a cycle pushes the removal; after cleanup with sync off the card can read
Pending changes 2. Sync a deletion pushes it while the repository is still configured. - An untouched Enable Git Sync switch follows the server: after another admin pauses sync, the next idle poll flips both the pill and the switch off, so turning it back on is one label click plus Save.
F24.unsupporteduses--automation-reffor the old Git tag; without access to that source it stays blocked. The current CLI also supports--automation-version Vfor a PyPI release (verified with 1.19.3 on 2026-10-10). Backend overrides come from these explicit launch flags; shellOH_AUTOMATION_VERSIONis not forwarded.- Arrange with
api ... --writeonly to mark automations changed or to simulate another admin; the save, check and sync steps must go through the form. - Known issue OpenHands/automation#586 (
F24.sync-now): a YAML-only repository edit can replace an unchanged uploaded script bundle because canonical gzip bytes retain their creation second. Recheck a disposable Git Sync repository and script automation, change only YAML, arrange the canonical rebuilds across different seconds, and compare the real saved upload reference before and after sync. Equal decompressed contents or a serializer-only probe do not prove that Canvas reuses its upload.
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.