Workspace drawer: files and changes
F08 · 34 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.
F08 Workspace drawer: files and changes
34 behaviors · 37 scripts & contracts · Maintained source
Entry points & prerequisites
What this family covers
Next to the chat, a conversation has a right-hand drawer with tabs. This family covers the drawer itself (toggle, tab bar, overflow menu with pinning, the phone panel page) and its two workspace tabs. Files is a read-only browser of the conversation's working directory: a path row with copy, a strip of open files, a collapsible tree, and a Rich/Plain viewer for code, Markdown, HTML, images and binaries. Commits lists an "Uncommitted" row and the git history, newest first. Each expands into per-file Monaco diffs with old/diff/new modes. The Terminal, Browser, Planner, Task list and Usage tab bodies belong to F27. The Files tab has no upload, download or edit.
How a user gets here
- Chat header: the panel toggle (
right-panel-toggle, block-drawer icon, right of the overviewibutton). At desktop width it shows or hides the drawer; at 1024 px or narrower it navigates to the panel page. - Drawer tab bar (
tabs-pane-header):conversation-tab-files,conversation-tab-commits. The ellipsis (tabs-pane-header >> testid=ellipsis-button) opens the menu withconversation-tabs-menu-open-<tab>andconversation-tabs-menu-pin-<tab>. - Direct URL
/conversations/<id>/panel(the phone panel page at 1024 px or narrower; wider, it redirects to/conversations/<id>with the drawer open). - Overview (
conversation-overview-toggle) → Changes row (conversation-overview-diffs) → Commits tab with Uncommitted expanded. - Files toolbar:
File discovery(file-discovery-settings, left offiles-tab-refresh) opens the File discovery dialog. - Workspace paths in agent messages written as inline code (
markdown-file-path-link), plus path chips in file-editor tool cards (file-path-chip) → Files tab with that file selected. - There is no command-menu entry or keyboard shortcut for the drawer or these tabs.
Given
Preconditions:
- Baseline state (launched, doctored,
onboard --skipdone) andcontrol-openhands llm preset deepseek(three short model turns, deepseek-flash). - A git fixture:
control-openhands fixture git-repo(creates<run>/workspace/qa-repowithREADME.md,src/calc.py,src/test_calc.pyand one commit byQA Fixture). - The conversation runs in that repo. From
/runcontrol-openhands browser click 'testid=open-workspace-button',control-openhands browser click 'testid=workspace-dropdown',control-openhands browser click 'role=button[name="+ Add Workspace"]'. The folder browser opens at<run>/private/home, so then runcontrol-openhands browser click 'testid=folder-browser-up'twice,control-openhands browser dblclick 'testid=folder-browser-entry-workspace',control-openhands browser dblclick 'testid=folder-browser-entry-qa-repo',control-openhands browser click 'testid=folder-browser-use'andcontrol-openhands browser click 'testid=workspace-launch-button'. The composer now shows aqa-repochip. Start with--stay, which keeps that chip; a plainconversation startnavigates home and loses it. - Seed changes with a prompt file, because the command contains quotes. Write
qa-f08-1.txt(anywhere outside the workspace) containing:In the current workspace directory run exactly this one bash command and then reply only "done": printf '# QA notes\n\nHello **world**\n' > qa-notes.md && printf '<h1>QA page</h1>\n' > qa-page.html && printf 'def sub(a, b):\n return a - b\n' >> src/calc.py && rm src/test_calc.py && printf 'PK\003\004' > qa-deck.pptx && head -c 64 /dev/urandom > qa-blob.bin && git status --short. Then runcontrol-openhands conversation start --stay --prompt "$(cat qa-f08-1.txt)" --wait --timeout 240. Read<id>from itsid(orcontrol-openhands browser url); the browser stays on/conversations/<id>. - Viewport desktop (1440×1000) unless a bullet says phone.
Open the drawerF08.panel-toggleMapped behavior
Scenario F08--open-the-drawer · Permalink · Source
Given
Use F08’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoUse browser tooltip as specified in the exact recipe.
- DoChoose “Right panel toggle”.
Expect
the chat-header toggle (Show panel/Hide panel, aria-pressed) opens and closes the drawer; the first open shows Files.
Exact CLI recipe & bug checks
Run control-openhands browser attr 'testid=right-panel-toggle' aria-pressed (false) and control-openhands browser tooltip 'testid=right-panel-toggle' (Show panel). Then run control-openhands browser click 'testid=right-panel-toggle', followed by control-openhands browser attr 'testid=right-panel-toggle' aria-label and control-openhands browser visible 'testid=files-tab'. The label is Hide panel, aria-pressed is true, and Files is visible with the workspace path and tree (browser screenshot --feature F08.panel-toggle --name drawer-open-files).
Switch and close by tabF08.tab-barMapped behavior
Scenario F08--switch-and-close-by-tab · Permalink · Source
Given
Use F08’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 “Conversation tab commits”.
- WaitWait for “Commit list” to appear.
Expect
the tab bar (Files, Commits, Planner, Terminal, Browser, Usage; Task list only when the agent has one) switches the drawer body, and only the active tab shows its label. Clicking the active tab closes the drawer.
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=conversation-tab-commits' and control-openhands browser wait 'testid=commit-list' --state visible --timeout 10000 (the first open shows Loading changes... for a moment, so an immediate browser visible can read false). Click testid=conversation-tab-commits again. right-panel-toggle aria-pressed becomes false and control-openhands browser visible 'testid=tabs-pane-header' is false. Reopen with right-panel-toggle.
ReloadF08.drawer-state-reloadMapped behavior
Scenario F08--reload · Permalink · Source
Given
Use F08’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.
Expect
after a reload the drawer starts closed, and reopening it restores the last selected tab for that conversation.
Exact CLI recipe & bug checks
With Commits selected, run control-openhands browser reload, then control-openhands browser attr 'testid=right-panel-toggle' aria-pressed (false; the drawer is closed). Click testid=right-panel-toggle; control-openhands browser visible 'testid=commit-list' is true.
Pin/unpinF08.tabs-pinMapped behavior
Scenario F08--pin-unpin · Permalink · Source
Given
Use F08’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 “Conversation tabs menu pin usage”.
- DoUse browser mouse click as specified in the exact recipe.
- DoReload the page.
- DoChoose “Conversation tabs menu open files”.
Expect
the pill on each menu row pins or unpins a tab. Unpinning the active tab selects another pinned one, and the choice persists across reloads.
Exact CLI recipe & bug checks
With Usage active, open the menu and run control-openhands browser click 'testid=conversation-tabs-menu-pin-usage', then control-openhands browser attr 'testid=conversation-tabs-menu-pin-usage' aria-label (Pin tab to bar; aria-pressed is false). Close the menu with browser mouse-click 560 600, then run control-openhands browser count 'testid=tabs-pane-header >> testid=conversation-tab-usage'. The count is 0 and Planner is now active. Run control-openhands browser reload and reopen the drawer; the count is still 0. To restore, open the menu, click testid=conversation-tabs-menu-pin-usage (label Unpin tab from bar), then control-openhands browser click 'testid=conversation-tabs-menu-open-files'. The usage tab count is 1 again.
ResizeF08.drawer-resizeMapped behavior
Scenario F08--resize · Permalink · Source
Given
Use F08’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoUse browser drag as specified in the exact recipe.
- DoReload the page.
- DoUse browser drag as specified in the exact recipe.
Expect
dragging the chat/drawer divider resizes both panes (chat 30–80 %, default 50 %, stored in desktop-layout-panel-width).
Exact CLI recipe & bug checks
The divider has no test id; its grip is the first .cursor-ew-resize on the page (the second is the Files tree divider). With the drawer open, control-openhands browser bbox 'testid=chat-interface' and control-openhands browser bbox 'testid=tabs-pane-header' are about 570 px wide each and control-openhands browser eval "localStorage.getItem('desktop-layout-panel-width')" is "50". Run control-openhands browser drag '.cursor-ew-resize >> nth=0' --by 200,0: the chat is 770 px, the drawer about 369 px and the key about 67.5. --by 900,0 clamps at "80", --by -1200,0 at "30" (chat 342 px), and after browser reload plus the toggle the chat is still 342 px. Restore with control-openhands browser drag '.cursor-ew-resize >> nth=0' --by 228,0 (chat 570 px, key "50").
VS Code linkF08.vscode-linkKnown issueBlocked
Scenario F08--vs-code-link · Permalink · Source
Given
Use F08’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 in a runtime without a running VS Code server:
control-openhands browser count 'testid=drawer-vscode-link'is0(hidden, as designed), andcontrol-openhands api GET /api/vscode/statusexplains why (enabled: false, orrunning: falsewhen the agent-server has no editor binary, as in these stacks; see F26 Gotchas). It needs a runtime withenable_vscodeand a running editor, on an origin that serves/vscode/.
Expect
a VSCode button at the right of the tab bar opens the editor in a new window; it is hidden when the runtime has no editor.
Linked known issues
Known issue #16871 (F08.vscode-link): the shared transformVSCodeUrl still accepts arbitrary schemes, and the current Cloud use-unified-vscode-url.ts branch passes sandbox exposed_urls editor values through it without the Local origin gate. Live prerequisite: an authorized Cloud account/sandbox whose editor exposed_urls value can be controlled for safe URL-scheme rejection checks. Local inspection on 2026-10-12 at fa1f724ab573 found no Download via VS Code action in the real card menu: its only production caller does not supply the required showOptions prop. The current drawer opens with noopener,noreferrer. Neither the old opener exploit nor the remaining Cloud scheme scenario was reproduced. Evidence: backfill-18309-surface-rechecks/F04.download/vscode-card-action-absent-16871.png and its ARIA snapshot. Keep this linked to the live editor-link consumer; do not infer the whole issue resolved from the removed card action.
Known issue OpenHands/software-agent-sdk#5845 (F08.vscode-link): reserved characters in the workspace path can split the editor URL's unencoded folder parameter and open the wrong folder. Live prerequisite: a supported origin serving a genuinely enabled/running editor and an owned conversation in a path containing & or #. A direct URL-constructor call does not exercise the link; Cloud exposed_urls takes another path.
Exact CLI recipe & bug checks
Blocked in a runtime without a running VS Code server: control-openhands browser count 'testid=drawer-vscode-link' is 0 (hidden, as designed), and control-openhands api GET /api/vscode/status explains why (enabled: false, or running: false when the agent-server has no editor binary, as in these stacks; see F26 Gotchas). It needs a runtime with enable_vscode and a running editor, on an origin that serves /vscode/.
ArchivedF08.archived-disabledBlocked
Scenario F08--archived · Permalink · Source
Given
Use F08’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoUse browser tooltip as specified in the exact recipe.
Expect
on an archived conversation the toggle is disabled with the tooltip Unavailable for Archives.
Exact CLI recipe & bug checks
Blocked locally: archived status comes from a Cloud sandbox (sandbox_status). On such a conversation, browser enabled 'testid=right-panel-toggle' should be false and browser tooltip 'testid=right-panel-toggle' should read Unavailable for Archives.
Workspace pathF08.files-workspace-pathMapped behavior
Scenario F08--workspace-path · Permalink · Source
Given
Use F08’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 attr 'testid=files-tab-workspace-path-value' title(ends in/workspace/qa-repo). Clicktestid=files-tab-workspace-path >> testid=copy-to-clipboard. Immediately afterwards,control-openhands browser attr 'testid=files-tab-workspace-path >> testid=copy-to-clipboard' aria-labelisCopied to clipboardandbrowser enabledon the same button isfalse.control-openhands browser clipboardreturns the full path. About 3 s later the label isCopy to clipboardagain.
Expect
Workspace: <working_dir> with the full path in its tooltip; the copy button turns into Copied to clipboard for about 2 s.
Exact CLI recipe & bug checks
Run control-openhands browser attr 'testid=files-tab-workspace-path-value' title (ends in /workspace/qa-repo). Click testid=files-tab-workspace-path >> testid=copy-to-clipboard. Immediately afterwards, control-openhands browser attr 'testid=files-tab-workspace-path >> testid=copy-to-clipboard' aria-label is Copied to clipboard and browser enabled on the same button is false. control-openhands browser clipboard returns the full path. About 3 s later the label is Copy to clipboard again.
TreeF08.files-treeKnown issueBlocked
Scenario F08--tree · Permalink · Source
Given
Use F08’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 “Files tab refresh”.
- DoChoose “File tree dir src”.
- DoChoose “File tree file src/calc.py”.
Expect
a tree of folders, collapsed at first, and files. Clicking a file selects it and adds it to the open-file strip.
Linked known issues
Known issue #18108 (F08.files-tree): Files tab shows "No files in workspace" on cloud when the agent writes outside /workspace/project. Live prerequisite: An authenticated Enterprise Cloud sandbox, no repository selected, with files outside its default working directory.
Known issue #17773 (F08.files-tree): Files tab always shows "No files in workspace" on Windows local backend. Live prerequisite: A Windows local Agent Server using cmd.exe; this run is macOS.
Known issue #16331 (F08.files-tree): file preview panel stops working during streaming. Live prerequisite: A real streaming/tool-running conversation while selecting a second workspace file.
Exact CLI recipe & bug checks
Run control-openhands browser click 'testid=files-tab-refresh' first (see Gotchas: files the agent made with bash are missing until a refresh). Then control-openhands browser attr 'testid=file-tree-dir-src' aria-expanded (false), control-openhands browser click 'testid=file-tree-dir-src' (now true) and control-openhands browser click 'testid=file-tree-file-src/calc.py'. control-openhands browser text 'testid=file-content-viewer-highlighted' shows the 4 lines def add…return a - b, and testid=file-quick-row-item-src/calc.py exists.
Tree toggleF08.files-tree-toggleMapped behavior
Scenario F08--tree-toggle · Permalink · Source
Given
Use F08’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 “File quick row tree toggle”.
Expect
the list-tree button hides or shows the tree, and the choice persists per conversation. The tree is 224 px wide by default.
Exact CLI recipe & bug checks
Run control-openhands browser bbox 'testid=files-tab-tree' (width 224). Then control-openhands browser click 'testid=file-quick-row-tree-toggle', control-openhands browser attr 'testid=file-quick-row-tree-toggle' aria-label (Show file tree) and control-openhands browser count 'testid=files-tab-tree' (0). Reload and reopen the drawer: the count is still 0. Click the toggle again to restore (count 1).
Tree resizeF08.files-tree-resizeMapped behavior
Scenario F08--tree-resize · Permalink · Source
Given
Use F08’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoUse browser drag as specified in the exact recipe.
- DoReload the page.
Expect
dragging the divider between the tree and the preview resizes the tree (160–480 px, default 224) and the width persists across reloads (files-tab-tree-width in localStorage).
Exact CLI recipe & bug checks
Run control-openhands browser drag 'testid=files-tab-tree-resize-handle >> .cursor-ew-resize' --by 100,0; browser bbox 'testid=files-tab-tree' is 324 wide and control-openhands browser eval "localStorage.getItem('files-tab-tree-width')" is "324". --by 500,0 clamps at 480, --by -600,0 at 160, and the width survives browser reload plus the toggle. Restore with --by 64,0 (224).
File discoveryF08.files-discovery-settingsMapped behavior
Scenario F08--file-discovery · Permalink · Source
Given
Use F08’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 “File discovery settings”.
- DoEnter “-1” in the element identified in the CLI recipe.
- DoEnter “$(printf '.git\nnode_modules\nsrc')” in the element identified in the CLI recipe.
- DoChoose “Files tab refresh”.
- DoReload the page.
- DoPress Escape.
- SetupUse the phone viewport.
- DoOpen /conversations/<id>/panel.
- DoChoose “Conversation tab files”.
Expect
the Files toolbar's File discovery button opens a dialog (excluded directory patterns, maximum files, symbolic links) whose Save applies to every conversation on that workspace and backend; an excluded directory leaves the tree, Restore defaults brings the default patterns back, an invalid limit disables Save.
Exact CLI recipe & bug checks
With the drawer open on Files (open it with right-panel-toggle and click testid=conversation-tab-files only if Files is not already the active tab: clicking the active tab closes the drawer), run control-openhands browser click 'testid=file-discovery-settings' and control-openhands browser snapshot 'role=dialog[name="File discovery"]': Applies to this workspace on this backend, including other conversations using it., textbox Excluded directories (.git, node_modules, .venv, … target), spinbutton Maximum files (0 for unlimited) 2000, checkbox Include symbolic links to files, buttons Restore defaults, Cancel, Save. With D='role=dialog[name="File discovery"]': control-openhands browser fill "$D >> role=spinbutton" '-1' makes control-openhands browser enabled "$D >> role=button[name=\"Save\"]" false; 2000 makes it true. Run control-openhands browser fill "$D >> role=textbox" "$(printf '.git\nnode_modules\nsrc')" and click Save: the dialog closes, control-openhands api GET /api/settings holds workspace_file_discovery keyed by the workspace path with excludedPatterns .git, node_modules, src, and after control-openhands browser click 'testid=files-tab-refresh' control-openhands browser count 'testid=file-tree-dir-src' is 0, also after browser reload and reopening the drawer (browser screenshot --feature F08.files-discovery-settings --name dialog before saving). Restore: reopen the dialog, click Restore defaults (the textbox returns to the default list; the dialog stays open), then Save; after Refresh the file-tree-dir-src count is 1. control-openhands browser press Escape closes the dialog without saving. At phone width the dialog fits the viewport: on the 390 px /panel page (control-openhands browser viewport phone, browser goto /conversations/<id>/panel, browser click 'testid=conversation-tab-files', then file-discovery-settings) control-openhands browser bbox 'testid=file-discovery-settings-modal' is about 351 px wide at x about 20 with insideViewport true (520 px at desktop), and browser screenshot --feature F08.files-discovery-settings --name phone-fits shows the title, the textbox and Restore defaults, Cancel, Save whole. Measure the file-discovery-settings-modal test id: role=dialog is the full-screen backdrop. Focus starts in the dialog (the Excluded directories textbox) and Tab cycles its fields and buttons without leaving it.
Rich/PlainF08.files-rich-plainKnown issueBlocked
Scenario F08--rich-plain · Permalink · Source
Given
Use F08’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 “Files tab content mode toggle option plain”.
- DoReload the page.
Expect
Rich highlights code, renders Markdown and shows HTML in an iframe sandboxed without scripts. Plain shows highlighted source. The mode persists across reloads.
Linked known issues
Known issue #15501 (F08.files-rich-plain): Generated-page JavaScript was blocked by an iframe sandbox. Files Rich intentionally disables scripts; the report does not identify whether Files or Browser was used. Live prerequisite: The original preview panel and file URL or minimal page identifying the intended surface; Files Rich deliberately blocks scripts, while Browser is separate.
Known issue OpenHands/software-agent-sdk#5784 (F08.files-rich-plain, F08.files-fallbacks, F08.files-open-new-window): a path-prefixed Agent Server can set a workspace cookie whose Path omits the prefix, leaving HTML/image/file previews unauthorized. Live prerequisite: an authenticated path-prefixed owned Local server with OH_WEB_URL and a real cookie-authenticated preview; the ordinary root-path launch cannot exercise that cookie Path.
Exact CLI recipe & bug checks
Click testid=file-tree-file-qa-notes.md; testid=file-content-viewer-markdown exists. Click testid=file-tree-file-qa-page.html, then run control-openhands browser attr 'testid=file-content-viewer-iframe' sandbox (allow-same-origin) and control-openhands browser eval "document.querySelector('[data-testid=file-content-viewer-iframe]').contentDocument.body.innerText" (QA page). Run control-openhands browser click 'testid=files-tab-content-mode-toggle-option-plain'; control-openhands browser text 'testid=files-tab-content >> testid=file-content-viewer-highlighted' reads 1<h1>QA page</h1>. After browser reload and reopening, control-openhands browser attr 'testid=files-tab-content-mode-toggle-option-plain' aria-checked is true. Switch back with testid=files-tab-content-mode-toggle-option-rich.
Open in new windowF08.files-open-new-windowKnown issueBlocked
Scenario F08--open-in-new-window · Permalink · Source
Given
Use F08’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 “Files tab open in new window”.
- DoUse browser tabs as specified in the exact recipe.
- DoUse browser tab as specified in the exact recipe.
- DoUse browser close tab as specified in the exact recipe.
Expect
the external-link icon opens the file from the workspace fileserver in a new browser tab.
Linked known issues
Known issue OpenHands/software-agent-sdk#5784 (F08.files-rich-plain, F08.files-fallbacks, F08.files-open-new-window): a path-prefixed Agent Server can set a workspace cookie whose Path omits the prefix, leaving HTML/image/file previews unauthorized. Live prerequisite: an authenticated path-prefixed owned Local server with OH_WEB_URL and a real cookie-authenticated preview; the ordinary root-path launch cannot exercise that cookie Path.
Exact CLI recipe & bug checks
With qa-page.html selected, run control-openhands browser click 'testid=files-tab-open-in-new-window' and control-openhands browser tabs. Page 1's URL is /api/conversations/<id>/workspace/qa-page.html?v=<n>. Run control-openhands browser tab 1, control-openhands browser text 'body' (QA page) and control-openhands browser close-tab 1.
Open-file stripF08.files-open-tabsMapped behavior
Scenario F08--open-file-strip · Permalink · Source
Given
Use F08’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 “File quick row item qa notes.md”.
- DoChoose “File quick row close qa notes.md”.
- DoReload the page.
Expect
the open-file strip, each tab with a close X. Closing the selected file selects a neighbour. Open files and the selection survive a reload and do not leak into other conversations.
Exact CLI recipe & bug checks
With several files opened, run control-openhands browser attr 'testid=file-quick-row-item-qa-page.html' title (the path). Select qa-notes.md with control-openhands browser click 'testid=file-quick-row-item-qa-notes.md', then close it with control-openhands browser click 'testid=file-quick-row-close-qa-notes.md'. control-openhands browser eval "[...document.querySelectorAll('[data-testid^=file-quick-row-item-]')].filter(e=>e.getAttribute('aria-selected')==='true').map(e=>e.dataset.testid)" names the neighbour. Run browser reload, reopen the drawer and control-openhands browser text 'testid=file-quick-row': the same files remain. Opening a different conversation (see Commits empty state) shows no quick-row items: browser text 'testid=file-quick-row' is empty. browser testids omits strip tabs scrolled out of view; count them with browser eval "[...document.querySelectorAll('[data-testid^=file-quick-row-item-]')].length".
No selectionF08.files-no-selectionMapped behavior
Scenario F08--no-selection · Permalink · Source
Given
Use F08’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
- DoClose every open file with its
file-quick-row-close-<path>button.control-openhands browser text 'testid=files-tab-content'readsSelect a file to view its contents, andcontrol-openhands browser count 'testid=files-tab-content-mode-toggle'is0.
Expect
with no file selected the pane reads Select a file to view its contents and the Rich/Plain toggle is absent.
Exact CLI recipe & bug checks
Close every open file with its file-quick-row-close-<path> button. control-openhands browser text 'testid=files-tab-content' reads Select a file to view its contents, and control-openhands browser count 'testid=files-tab-content-mode-toggle' is 0.
Second agent turn: commit, binaries, auto-refreshF08.files-auto-refresh · F08.files-refreshKnown failureKnown issueBlocked
Scenario F08--second-agent-turn-commit-binaries-auto-refresh · Permalink · Source
Given
Use F08’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
- DoStart a conversation with the prompt “$(cat qa-f08-2.txt)”.
Expect
files the agent writes appear without a manual refresh.
Refresh files re-reads the file list and the selected file's content.
Known bug observations
Known failure (reproduced 2026-10-06): neither changes until Refresh, because the auto-refresh hook skips the file queries for shell commands on purpose (see Gotchas). Repro candidate, not yet filed; a report would ask to revisit that choice. Click testid=files-tab-refresh once: the list gains the three files and the open preview ends with edited by agent.
Linked known issues
Known issue #16949 (F08.files-refresh): Keep Files and Changes views source-driven after refresh. Live prerequisite: An authenticated Cloud repository for the refresh-query mismatch, or real file-editor observations causing an in-place M-to-D Git refetch.
Exact CLI recipe & bug checks
Keep Files open with qa-notes.md selected in Rich mode. Write qa-f08-2.txt with: In the current workspace directory run exactly this one bash command and then reply only "done": printf 'BIN\000\001' > qa-nul.bin && printf 'PK\003\004\000\000' > qa-real.docx && python3 -c "import base64;open('qa-dot.png','wb').write(base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='))" && printf '\n- edited by agent\n' >> qa-notes.md && git add qa-notes.md && git -c user.name=QA-Agent -c user.email=qa@example.invalid commit -qm 'QA agent commit' && git log --oneline. Run control-openhands conversation start --stay --prompt "$(cat qa-f08-2.txt)" --wait --timeout 240, then control-openhands browser testids 'testid=files-tab-tree' and control-openhands browser text 'testid=file-content-viewer-markdown'. Expected: qa-nul.bin, qa-real.docx and qa-dot.png are listed, and the preview ends with edited by agent. Known failure (reproduced 2026-10-06): neither changes until Refresh, because the auto-refresh hook skips the file queries for shell commands on purpose (see Gotchas). Repro candidate, not yet filed; a report would ask to revisit that choice. Click testid=files-tab-refresh once: the list gains the three files and the open preview ends with edited by agent.
FallbacksF08.files-fallbacksKnown issueBlocked
Scenario F08--fallbacks · Permalink · Source
Given
Use F08’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoAfter a refresh, click
testid=file-tree-file-qa-nul.bin(testid=file-content-viewer-binary-fallback, textBinary file – preview not available). Then clicktestid=file-tree-file-qa-real.docx(testid=file-content-viewer-unsupported-document,Preview isn't available for Word files.). Then clicktestid=file-tree-file-qa-dot.pngand runcontrol-openhands browser eval "document.querySelector('[data-testid=file-content-viewer-image] img').naturalWidth"(1). In Plain mode the PNG shows the binary fallback.qa-deck.pptxandqa-blob.binfrom the first prompt have no NUL byte and render as text (see Gotchas).
Expect
images render as <img> in Rich mode. Files with NUL bytes show Binary file – preview not available, and Office files show Preview isn't available for Word files. (or PowerPoint/Excel).
Linked known issues
Known issue OpenHands/software-agent-sdk#5784 (F08.files-rich-plain, F08.files-fallbacks, F08.files-open-new-window): a path-prefixed Agent Server can set a workspace cookie whose Path omits the prefix, leaving HTML/image/file previews unauthorized. Live prerequisite: an authenticated path-prefixed owned Local server with OH_WEB_URL and a real cookie-authenticated preview; the ordinary root-path launch cannot exercise that cookie Path.
Exact CLI recipe & bug checks
After a refresh, click testid=file-tree-file-qa-nul.bin (testid=file-content-viewer-binary-fallback, text Binary file – preview not available). Then click testid=file-tree-file-qa-real.docx (testid=file-content-viewer-unsupported-document, Preview isn't available for Word files.). Then click testid=file-tree-file-qa-dot.png and run control-openhands browser eval "document.querySelector('[data-testid=file-content-viewer-image] img').naturalWidth" (1). In Plain mode the PNG shows the binary fallback. qa-deck.pptx and qa-blob.bin from the first prompt have no NUL byte and render as text (see Gotchas).
File-editor edits auto-refresh; deleted file errorsF08.files-auto-refresh · F08.files-load-errorKnown issueBlocked
Scenario F08--file-editor-edits-auto-refresh-deleted-file-errors · Permalink · Source
Given
Use F08’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
- DoStart a conversation with the prompt “Do two steps, then reply only "done". Step 1: run the bash command: rm qa-blob.bin. Step 2: use your file editor tool (not bash) to create the file qa-editor.txt in the current workspace directory with the single line: created by file editor”.
Expect
files the agent writes appear without a manual refresh.
a selected file that can no longer be read shows the error (Failed to read <path>: 404).
Linked known issues
Known issue #16689 (F08.files-load-error): When workspace/ paths throw a 401, call the workspace-session endpoint. Live prerequisite: The original fresh-VM URL, auth/cookie state and exact request sequence; the report says the trigger is unclear.
Known issue #15567 (F08.files-load-error): Workspace file preview returns 401 Unauthorized on non-HTTPS non-loopback hosts. Live prerequisite: A plain-HTTP non-loopback deployment and browser accessing that origin, with workspace-cookie observation.
Known issue #15519 (F08.files-load-error): "files" tab in Files side panel throws 401 Unauthorized. Live prerequisite: The reported Linux Docker deployment/auth topology; this isolated run uses the macOS source launcher.
Known issue OpenHands/software-agent-sdk#4562 (F08.files-load-error): Plain-HTTP non-loopback workspace previews can return401 after the browser rejects the workspace-session cookie. Live prerequisite: A plain-HTTP non-loopback same-origin browser deployment with real workspace-cookie observation.
Exact CLI recipe & bug checks
Keep Files open with testid=file-tree-file-qa-blob.bin selected. Run control-openhands conversation start --stay --prompt 'Do two steps, then reply only "done". Step 1: run the bash command: rm qa-blob.bin. Step 2: use your file editor tool (not bash) to create the file qa-editor.txt in the current workspace directory with the single line: created by file editor' --wait --timeout 240. Without clicking Refresh, control-openhands browser count 'testid=file-tree-file-qa-editor.txt' is 1 and browser count 'testid=file-tree-file-qa-blob.bin' is 0 (the editor observation refetches the whole list). qa-blob.bin stays in the open-file strip, and the same observation re-reads the open file: control-openhands browser text 'testid=file-content-viewer-error' reads Failed to read qa-blob.bin: 404 and control-openhands browser network --filter qa-blob shows the 404 (browser screenshot --feature F08.files-load-error --name deleted-open-file).
Open from chatF08.files-open-from-chatMapped behavior
Scenario F08--open-from-chat · Permalink · Source
Given
Use F08’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 quoted command in the exact recipe.
- DoChoose “Agent message” / nth=-1 / “Markdown file path”.
- DoChoose “File quick row item qa notes.md”.
- SetupUse the phone viewport.
- SetupUse the desktop viewport.
- WaitWait for the element identified in the CLI recipe to appear.
Expect
clicking a workspace path in an agent (or user) message opens the drawer on Files with that file selected.
Exact CLI recipe & bug checks
Run control-openhands conversation start --stay --prompt 'Without running any tool, reply with exactly this text and nothing else: See src/calc.py for the code.' --wait --timeout 180. Close the drawer, then run control-openhands browser click 'testid=agent-message >> nth=-1 >> testid=markdown-file-path-link'. right-panel-toggle aria-pressed turns true, testid=files-tab is visible and file-quick-row-item-src/calc.py is the selected tab. The same path in the prompt bubble works too: close the drawer and click 'testid=user-message >> testid=markdown-file-path-link'. Phone: select another file (control-openhands browser click 'testid=file-quick-row-item-qa-notes.md'), run control-openhands browser viewport phone, then the same agent-message click with --expect-url '/panel$': the URL becomes /conversations/<id>/panel, browser visible 'testid=files-tab' is true and control-openhands browser attr 'testid=file-quick-row-item-src/calc.py' aria-selected is true (browser screenshot --feature F08.files-open-from-chat --name phone-after-click). Return with control-openhands browser viewport desktop: the app replaces /panel with /conversations/<id> (control-openhands browser wait-url '/conversations/[^/]+$') and shows the drawer beside the chat.
Tool-card path chipF08.files-open-from-tool-chipMapped behavior
Scenario F08--tool-card-path-chip · Permalink · Source
Given
Use F08’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 “2 actions completed”.
- DoChoose “Collapse actions” / “Expand” / nth=-1.
- DoChoose “File path chip”.
Expect
the path chip on a file-editor tool card (absolute path) opens the drawer on Files with that file selected.
Exact CLI recipe & bug checks
After the file-editor turn above, close the drawer and expand its tool group and card: control-openhands browser click 'text=2 actions completed', then control-openhands browser click 'role=region[name="Collapse actions"] >> role=button[name="Expand"] >> nth=-1' (the Create qa-editor.txt card). control-openhands browser text 'testid=file-path-chip' is the absolute <run>/workspace/qa-repo/qa-editor.txt. Run control-openhands browser click 'testid=file-path-chip': right-panel-toggle aria-pressed turns true, the selected strip tab is file-quick-row-item-qa-editor.txt (relative) and browser text 'testid=files-tab-content' ends with created by file editor.
Commits listF08.uncommitted-changes · F08.commit-rowsKnown issueBlocked
Scenario F08--commits-list · Permalink · Source
Given
Use F08’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 “Uncommitted changes row toggle”.
- DoChoose “Commit row” / containing “Initial fixture commit” / “Commit row toggle”.
Expect
the top row reads -, Uncommitted and a file count; expanding it lists the changed paths.
each commit row shows the short SHA, subject, author (only when there are several authors) and relative time. Rows are single-open accordions, and new agent commits appear without a reload.
Linked known issues
Known issue #16716 (F08.uncommitted-changes): Diff view is regularly out of sync. Live prerequisite: The original simultaneous-agent/shared-repository state or a deterministic stale-diff trigger; the report only suggests that timing.
Known issue #16454 (F08.uncommitted-changes): Diff list is showing duplicate entries. Live prerequisite: The original Git changes response and update sequence producing duplicate rows; the report provides no trigger.
Known issue #15692 (F08.uncommitted-changes): Diff still shows after agent has committed and pushed. Live prerequisite: A real agent commit/push terminal observation while the diff remains mounted, plus a disposable Git remote.
Exact CLI recipe & bug checks
Click testid=conversation-tab-commits and run control-openhands browser text 'testid=commit-list'. After the first prompt it reads -, Uncommitted, 6 files, then 4bf8272-style SHA, Initial fixture commit and … ago; after the second prompt QA agent commit appears above it, with author names QA-Agent and QA Fixture shown because there are now two authors. Run control-openhands browser click 'testid=uncommitted-changes-row-toggle' and control-openhands browser text 'testid=uncommitted-changes-row-content' (the changed paths; aria-expanded is true). Run control-openhands browser click 'testid=commit-row >> has-text=Initial fixture commit >> testid=commit-row-toggle'; Uncommitted's aria-expanded turns false, and control-openhands browser text 'testid=commit-row-content' lists README.md, src/calc.py, src/test_calc.py. After the third prompt the Uncommitted count is 8 files.
Diff modesF08.diff-view-modesMapped behavior
Scenario F08--diff-modes · Permalink · Source
Given
Use F08’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 “Uncommitted changes row content” / “File diff viewer outer” / containing “src/calc.py” / “Collapse”.
Expect
expanding a changed file shows its diff; the view-mode buttons (Old version, Diff, New version) switch between old, diff and new without page errors.
Exact CLI recipe & bug checks
In the expanded Uncommitted row, run control-openhands browser click 'testid=uncommitted-changes-row-content >> testid=file-diff-viewer-outer >> has-text=src/calc.py >> testid=collapse'. control-openhands browser text 'testid=uncommitted-changes-row-content >> testid=file-diff-viewer-outer >> has-text=src/calc.py >> testid=editor-container' shows the added def sub lines, and view-mode-diff has aria-pressed true. The row's buttons are named: control-openhands browser snapshot 'testid=uncommitted-changes-row-content >> testid=file-diff-viewer-outer >> has-text=src/calc.py' lists button "Old version", button "Diff" [pressed], button "New version" and button "Collapse" (the same collapse button reads Expand while the row is collapsed), so … >> role=button[name="Old version"] works as well as the test id. Click … >> testid=view-mode-old (2 lines), then … >> testid=view-mode-new (4 lines), then view-mode-diff. Run control-openhands browser errors --app-only before and after: each collapse and each diff→old switch adds TextModel got disposed before DiffEditorWidget model got reset (fail, #17567).
Markdown previewF08.diff-markdown-previewMapped behavior
Scenario F08--markdown-preview · Permalink · Source
Given
Use F08’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 “Commit row” / containing “QA agent commit” / “Commit row toggle”.
Expect
a Markdown file in new (or old) mode renders as a prose preview.
Exact CLI recipe & bug checks
In this order qa-notes.md is already committed, so check it in the QA agent commit row: control-openhands browser click 'testid=commit-row >> has-text=QA agent commit >> testid=commit-row-toggle', then with F='testid=commit-row >> has-text=QA agent commit >> testid=file-diff-viewer-outer >> has-text=qa-notes.md' click "$F >> testid=collapse" and "$F >> testid=view-mode-new". control-openhands browser count "$F >> testid=markdown-preview" is 1, rendering <h1>QA notes</h1>, world in bold and the edited by agent list item. (Before the second prompt the same check works under testid=uncommitted-changes-row-content.) Re-expand Uncommitted with uncommitted-changes-row-toggle for the next bullet.
Deleted fileF08.diff-deleted-fileMapped behavior
Scenario F08--deleted-file · Permalink · Source
Given
Use F08’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
- DoExpand
src/test_calc.pyin Uncommitted.control-openhands browser text 'testid=uncommitted-changes-row-content >> testid=file-diff-viewer-outer >> has-text=src/test_calc.py >> testid=file-deleted-message'readsThis file was deleted. Its previous contents are no longer available for preview.
Expect
a deleted working-tree file reads This file was deleted. Its previous contents are no longer available for preview.
Exact CLI recipe & bug checks
Expand src/test_calc.py in Uncommitted. control-openhands browser text 'testid=uncommitted-changes-row-content >> testid=file-diff-viewer-outer >> has-text=src/test_calc.py >> testid=file-deleted-message' reads This file was deleted. Its previous contents are no longer available for preview.
From the overviewF08.changes-from-overviewMapped behavior
Scenario F08--from-the-overview · Permalink · Source
Given
Use F08’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 “Conversation overview toggle”.
- DoChoose “Conversation overview diffs”.
- DoReload the page.
Expect
the overview's Changes row opens the drawer on Commits with Uncommitted expanded and closes the overview.
Exact CLI recipe & bug checks
With the drawer closed, run control-openhands browser click 'testid=conversation-overview-toggle' and control-openhands browser click 'testid=conversation-overview-diffs' (its siblings conversation-overview-diffs-additions/-deletions show +N/-N; deleted files are not counted). right-panel-toggle aria-pressed is true, testid=commit-list is visible, uncommitted-changes-row-toggle has aria-expanded true and testid=conversation-overview-panel is hidden. After browser reload and the toggle, Commits is still selected.
Empty statesF08.commits-states · F08.files-empty-workspaceMapped behavior
Scenario F08--empty-states · Permalink · Source
Given
Use F08’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
- SetupCreate the disposable folder fixture specified in the recipe.
- DoOpen /.
- DoStart a conversation with the prompt “Run exactly this bash command, then reply only "done": rm notes.md && git status --short”.
- DoReload the page.
- DoChoose “Conversation tab commits”.
- SetupChange the run-owned service state: stop agent-server.
Expect
with no commits and no changes the Commits tab shows No commits yet. While the runtime is inactive it shows Waiting for runtime to start..., and while loading Loading changes....
a workspace with no files shows No files in workspace.
Exact CLI recipe & bug checks
Run control-openhands fixture folder, which makes <run>/workspace/qa-folder, a plain folder (no .git) with notes.md; by the time the conversation has run it holds an empty .git (seen live; not created by the prompt), so it ends up a repo with no commits. Run control-openhands browser goto / (the browser is on the qa-repo conversation) and pick it with the workspace picker as in Preconditions (folder-browser-entry-qa-folder). Then run control-openhands conversation start --stay --prompt 'Run exactly this bash command, then reply only "done": rm notes.md && git status --short' --wait --timeout 180. Open the drawer and click testid=conversation-tab-commits; control-openhands browser count 'text="No commits yet"' is 1 (browser screenshot --feature F08.commits-states --name no-commits). On Files, click testid=files-tab-refresh; control-openhands browser text 'testid=files-tab-tree' reads No files in workspace. Loading changes... (testid=commits-tab-status) shows briefly on the first Commits open after a load: on /conversations/<id> with Files selected, browser reload, open the drawer, then control-openhands browser click 'testid=conversation-tab-commits' --observe 'testid=commits-tab-status' --observe-ms 4000 records Loading changes... for a few ms. Waiting for runtime to start... (agent state init/loading/error) was not reached: service stop agent-server and restart both left No commits yet on screen.
Commit capF08.commits-capMapped behavior
Scenario F08--commit-cap · Permalink · Source
Given
Use F08’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 /conversations/<id>.
- DoReload the page.
Expect
the history loads the latest 50 commits; a longer history ends with Showing the latest 50 commits.
Exact CLI recipe & bug checks
Arrange more than 50 commits in the fixture (read-only for the UI): for i in $(seq 1 50); do git -C "$OH_VERIFY_RUN/workspace/qa-repo" -c user.name=QA-Bulk -c user.email=qa@example.invalid commit -q --allow-empty -m "QA bulk $i"; done. On the qa-repo conversation (control-openhands browser goto /conversations/<id>; Empty states left the qa-folder conversation) run browser reload, open the drawer on Commits and wait for testid=commit-list. control-openhands browser text 'testid=commit-list-cap-notice' reads Showing the latest 50 commits and browser count 'testid=commit-row' is 50. Do this last: it changes the commit list the other bullets read.
Phone panel pageF08.phone-panel-pageMapped behavior
Scenario F08--phone-panel-page · Permalink · Source
Given
Use F08’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.
- DoChoose “Right panel toggle”.
- DoChoose “Conversation mobile panel back”.
- SetupUse the desktop viewport.
Expect
at 1024 px or narrower the toggle navigates to /conversations/<id>/panel, a full-screen page with a Back chevron, the same tab bar and the tab body.
Exact CLI recipe & bug checks
Run control-openhands browser viewport phone on /conversations/<id>, then control-openhands browser click 'testid=right-panel-toggle' --expect-url '/panel$'. control-openhands browser bbox 'testid=files-tab' gives insideViewport true and pageHorizontalOverflow false, with Commits diffs usable at 390 px (browser screenshot --feature F08.phone-panel-page --name panel). control-openhands browser attr 'testid=conversation-mobile-panel-back' aria-label is Back. Run control-openhands browser click 'testid=conversation-mobile-panel-back' --expect-url '/conversations/[^/]+$'; the chat is visible. Return with control-openhands browser viewport desktop.
Direct panel URLF08.panel-direct-urlMapped behavior
Scenario F08--direct-panel-url · Permalink · Source
Given
Use F08’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 /conversations/<id>/panel.
- SetupUse the phone viewport.
- DoChoose “Conversation mobile panel back”.
- SetupUse the desktop viewport.
Expect
opening /conversations/<id>/panel directly renders the panel page at 1024 px or narrower, and Back returns to the chat; wider, the URL is replaced with /conversations/<id> and the drawer opens beside the chat.
Exact CLI recipe & bug checks
At desktop run control-openhands browser goto /conversations/<id>/panel. The app replaces the URL with /conversations/<id> (the goto result already shows it; control-openhands browser url confirms), control-openhands browser count 'testid=conversation-mobile-panel-back' is 0, browser visible 'testid=chat-interface' is true and right-panel-toggle aria-pressed is true (the drawer opens beside the chat; browser screenshot --feature F08.panel-direct-url --name desktop-redirect). Then run control-openhands browser viewport phone and the same goto: the URL keeps /panel, the conversation-mobile-panel-back count is 1 and chat-interface is not visible. control-openhands browser click 'testid=conversation-mobile-panel-back' --expect-url '/conversations/[^/]+$' returns to the chat. Return with control-openhands browser viewport desktop.
ErrorsF08 · setup / cleanupSupport step
Scenario F08--errors · Permalink · Source
Given
Use F08’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.
Do — reading outline
- DoAfter each group run
control-openhands browser errors --app-only. The Monaco disposal error above belongs to this family. 404s on/api/llm/balanceand.agents_tmp/downloads come from the Usage and Planner tabs (F27).
Expect
Complete this support step before continuing to the next scenario.
Exact CLI recipe & bug checks
After each group run control-openhands browser errors --app-only. The Monaco disposal error above belongs to this family. 404s on /api/llm/balance and .agents_tmp/ downloads come from the Usage and Planner tabs (F27).
Rich fenced codeF08.files-rich-plainKnown failureKnown issueBlocked
Scenario F08--rich-fenced-code · Permalink · Source
Given
Use F08’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
- DoArrange a Markdown file in the selected workspace containing a heading and a fenced Python
print("qa")block. Open it from Files in Rich mode and takebrowser screenshot --feature F08.files-rich-plain --name fenced-code. Known failure (reproduced 2026-10-12, #16802): the code renders in nested dark/gray containers with repeated padding. This is a layout-only observation; the measuredpreand prose ancestor had zero border width, so it is not a claim of visible CSS borders.
Expect
Rich highlights code, renders Markdown and shows HTML in an iframe sandboxed without scripts. Plain shows highlighted source. The mode persists across reloads.
Known bug observations
Known failure (reproduced 2026-10-12, #16802): the code renders in nested dark/gray containers with repeated padding. This is a layout-only observation; the measured pre and prose ancestor had zero border width, so it is not a claim of visible CSS borders.
Linked known issues
Known issue #15501 (F08.files-rich-plain): Generated-page JavaScript was blocked by an iframe sandbox. Files Rich intentionally disables scripts; the report does not identify whether Files or Browser was used. Live prerequisite: The original preview panel and file URL or minimal page identifying the intended surface; Files Rich deliberately blocks scripts, while Browser is separate.
Known issue OpenHands/software-agent-sdk#5784 (F08.files-rich-plain, F08.files-fallbacks, F08.files-open-new-window): a path-prefixed Agent Server can set a workspace cookie whose Path omits the prefix, leaving HTML/image/file previews unauthorized. Live prerequisite: an authenticated path-prefixed owned Local server with OH_WEB_URL and a real cookie-authenticated preview; the ordinary root-path launch cannot exercise that cookie Path.
Exact CLI recipe & bug checks
Arrange a Markdown file in the selected workspace containing a heading and a fenced Python print("qa") block. Open it from Files in Rich mode and take browser screenshot --feature F08.files-rich-plain --name fenced-code. Known failure (reproduced 2026-10-12, #16802): the code renders in nested dark/gray containers with repeated padding. This is a layout-only observation; the measured pre and prose ancestor had zero border width, so it is not a claim of visible CSS borders.
Git filename with a spaceF08.uncommitted-changesKnown failureKnown issueBlocked
Scenario F08--git-filename-with-a-space · Permalink · Source
Given
Use F08’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
- DoIn a disposable workspace Git fixture, create and commit
my notes.md, then modify it (plain shell Git/file commands are fixture setup, not UI proof). Open its empty Local conversation's Commits tab and readbrowser network --filter '/git/'. Expected: the modified path and other changes stay listed. Known failure (reproduced 2026-10-12, OpenHands/software-agent-sdk#5642): the scoped changes call returns 400 and hides the Uncommitted changes. Only the space-name case was tested; the same issue's non-ASCII and rename cases still require their own checks.
Expect
the top row reads -, Uncommitted and a file count; expanding it lists the changed paths.
Known bug observations
Known failure (reproduced 2026-10-12, OpenHands/software-agent-sdk#5642): the scoped changes call returns 400 and hides the Uncommitted changes. Only the space-name case was tested; the same issue's non-ASCII and rename cases still require their own checks.
Linked known issues
Known issue #16716 (F08.uncommitted-changes): Diff view is regularly out of sync. Live prerequisite: The original simultaneous-agent/shared-repository state or a deterministic stale-diff trigger; the report only suggests that timing.
Known issue #16454 (F08.uncommitted-changes): Diff list is showing duplicate entries. Live prerequisite: The original Git changes response and update sequence producing duplicate rows; the report provides no trigger.
Known issue #15692 (F08.uncommitted-changes): Diff still shows after agent has committed and pushed. Live prerequisite: A real agent commit/push terminal observation while the diff remains mounted, plus a disposable Git remote.
Exact CLI recipe & bug checks
In a disposable workspace Git fixture, create and commit my notes.md, then modify it (plain shell Git/file commands are fixture setup, not UI proof). Open its empty Local conversation's Commits tab and read browser network --filter '/git/'. Expected: the modified path and other changes stay listed. Known failure (reproduced 2026-10-12, OpenHands/software-agent-sdk#5642): the scoped changes call returns 400 and hides the Uncommitted changes. Only the space-name case was tested; the same issue's non-ASCII and rename cases still require their own checks.
File listing above one MiBF08.files-treeKnown failureKnown issueBlocked
Scenario F08--file-listing-above-one-mib · Permalink · Source
Given
Use F08’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
- DoIn a disposable fixture, arrange 1800 tiny files beneath three 220-character directory segments so the sorted relative-path listing exceeds one MiB while remaining below the 2000-file cap. Keep a normal tracked README at the root. Open Files, click
testid=files-tab-refresh, then countrole=button[name="README.md"][exact]and inspect the tree. Known failure (reproduced 2026-10-12, OpenHands/software-agent-sdk#5638): the README disappears and a spurious partial-path folder appears because the command API returns the tail output chunk only. The greater-than-100-chunk/null-exit case was not driven.
Expect
a tree of folders, collapsed at first, and files. Clicking a file selects it and adds it to the open-file strip.
Known bug observations
Known failure (reproduced 2026-10-12, OpenHands/software-agent-sdk#5638): the README disappears and a spurious partial-path folder appears because the command API returns the tail output chunk only. The greater-than-100-chunk/null-exit case was not driven.
Linked known issues
Known issue #18108 (F08.files-tree): Files tab shows "No files in workspace" on cloud when the agent writes outside /workspace/project. Live prerequisite: An authenticated Enterprise Cloud sandbox, no repository selected, with files outside its default working directory.
Known issue #17773 (F08.files-tree): Files tab always shows "No files in workspace" on Windows local backend. Live prerequisite: A Windows local Agent Server using cmd.exe; this run is macOS.
Known issue #16331 (F08.files-tree): file preview panel stops working during streaming. Live prerequisite: A real streaming/tool-running conversation while selecting a second workspace file.
Exact CLI recipe & bug checks
In a disposable fixture, arrange 1800 tiny files beneath three 220-character directory segments so the sorted relative-path listing exceeds one MiB while remaining below the 2000-file cap. Keep a normal tracked README at the root. Open Files, click testid=files-tab-refresh, then count role=button[name="README.md"][exact] and inspect the tree. Known failure (reproduced 2026-10-12, OpenHands/software-agent-sdk#5638): the README disappears and a spurious partial-path folder appears because the command API returns the tail output chunk only. The greater-than-100-chunk/null-exit case was not driven.
Subdirectory Git pathsF08.uncommitted-changes · F08.diff-view-modesKnown failureKnown issueBlocked
Scenario F08--subdirectory-git-paths · Permalink · Source
Given
Use F08’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
- DoExpected: a workspace at
repo/srclists changes beneath that folder relative to it, and its diffs open. Known failure (reproduced 2026-10-16, SDK 1.55.0, OpenHands/software-agent-sdk#5843): tracked paths retain the repository prefix while untracked paths are relative tosrc; opening displayedsrc/app.pyrequestsrepo/src/src/app.pyand returns400. The ordinary repository-root control lists correct paths. The recipe creates only disposable, idle offline conversations.
Expect
the top row reads -, Uncommitted and a file count; expanding it lists the changed paths.
expanding a changed file shows its diff; the view-mode buttons (Old version, Diff, New version) switch between old, diff and new without page errors.
Known bug observations
Known failure (reproduced 2026-10-16, SDK 1.55.0, OpenHands/software-agent-sdk#5843): tracked paths retain the repository prefix while untracked paths are relative to src; opening displayed src/app.py requests repo/src/src/app.py and returns400. The ordinary repository-root control lists correct paths. The recipe creates only disposable, idle offline conversations.
Linked known issues
Known issue #16716 (F08.uncommitted-changes): Diff view is regularly out of sync. Live prerequisite: The original simultaneous-agent/shared-repository state or a deterministic stale-diff trigger; the report only suggests that timing.
Known issue #16454 (F08.uncommitted-changes): Diff list is showing duplicate entries. Live prerequisite: The original Git changes response and update sequence producing duplicate rows; the report provides no trigger.
Known issue #15692 (F08.uncommitted-changes): Diff still shows after agent has committed and pushed. Live prerequisite: A real agent commit/push terminal observation while the diff remains mounted, plus a disposable Git remote.
Exact CLI recipe & bug checks
Expected: a workspace at repo/src lists changes beneath that folder relative to it, and its diffs open. Known failure (reproduced 2026-10-16, SDK 1.55.0, OpenHands/software-agent-sdk#5843): tracked paths retain the repository prefix while untracked paths are relative to src; opening displayed src/app.py requests repo/src/src/app.py and returns400. The ordinary repository-root control lists correct paths. The recipe creates only disposable, idle offline conversations.
sh known-bug F08.diff-view-modes subdirectory-paths
control-openhands browser viewport desktop
QA_ID="$(python3 - <<'PY'
import json, os, pathlib, subprocess, tempfile
base = pathlib.Path(os.environ["OH_VERIFY_RUN"]) / "workspace"
base.mkdir(exist_ok=True)
fixture = pathlib.Path(tempfile.mkdtemp(prefix="qa-surface-", dir=base))
llm = {"model":"openai/gpt-4o", "base_url":"http://127.0.0.1:1/v1", "api_key":"qa-not-real", "num_retries":0, "timeout":2}
def create(workspace, parent=None, tags=None):
payload = {"agent":{"kind":"Agent", "llm":llm, "tools":[]}, "workspace":{"working_dir":str(workspace)}, "autotitle":False}
if parent: payload["parent_conversation_id"] = parent
if tags: payload["tags"] = tags
request = fixture / "request.json"
request.write_text(json.dumps(payload))
result = subprocess.run(["control-openhands", "api", "POST", "/api/conversations", "--write", "--data", "@"+str(request), "--pick", "id"], check=True, capture_output=True, text=True)
return json.loads(result.stdout)["value"]
def git(directory, *args):
subprocess.run(["git", "-C", str(directory), *args], check=True, capture_output=True)
repo = fixture / "repo"
(repo / "src").mkdir(parents=True)
git(repo, "init", "-q", "-b", "main")
(repo / "README.md").write_text("root before\n")
(repo / "src/app.py").write_text("x = 1\n")
git(repo, "add", ".")
git(repo, "-c", "user.name=QA", "-c", "user.email=qa@example.invalid", "commit", "-qm", "Owned fixture")
(repo / "README.md").write_text("root after\n")
(repo / "src/app.py").write_text("x = 2\n")
(repo / "src/new.py").write_text("new\n")
print(create(repo / "src"))
PY
)"
control-openhands browser goto "/conversations/$QA_ID"
control-openhands browser click 'testid=right-panel-toggle'
control-openhands browser click 'testid=conversation-tab-commits'
control-openhands browser wait-text Uncommitted --timeout 10000
control-openhands browser click 'testid=uncommitted-changes-row-toggle'
control-openhands browser network --clear
control-openhands browser click 'testid=file-diff-viewer-outer >> has-text=src/app.py >> testid=collapse' --observe 'testid=uncommitted-changes-row-content' --observe-ms 1000
control-openhands browser screenshot --feature F08.diff-view-modes --name subdir-mixed-paths
control-openhands assert --field recent.0.status --equals 200 --bug-shows 400 -- browser network --filter '/git/diff' # bug
Plain parent containing a repositoryF08.uncommitted-changesKnown failureKnown issueBlocked
Scenario F08--plain-parent-containing-a-repository · Permalink · Source
Given
Use F08’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
- DoExpected: a plain workspace holding one dirty repository exposes that repository's changes. Known failure (reproduced 2026-10-16, pinned SDK 1.55.0, OpenHands/software-agent-sdk#5641): the changes request returns200 with no changes and Commits reads
No commits yetalthough the child repository is dirty. The upstream fix is closed but not included in the current pin. The recipe removes only its own verified empty parent.git; never remove a user's repository.
Expect
the top row reads -, Uncommitted and a file count; expanding it lists the changed paths.
Known bug observations
Known failure (reproduced 2026-10-16, pinned SDK 1.55.0, OpenHands/software-agent-sdk#5641): the changes request returns200 with no changes and Commits reads No commits yet although the child repository is dirty. The upstream fix is closed but not included in the current pin. The recipe removes only its own verified empty parent .git; never remove a user's repository.
Linked known issues
Known issue #16716 (F08.uncommitted-changes): Diff view is regularly out of sync. Live prerequisite: The original simultaneous-agent/shared-repository state or a deterministic stale-diff trigger; the report only suggests that timing.
Known issue #16454 (F08.uncommitted-changes): Diff list is showing duplicate entries. Live prerequisite: The original Git changes response and update sequence producing duplicate rows; the report provides no trigger.
Known issue #15692 (F08.uncommitted-changes): Diff still shows after agent has committed and pushed. Live prerequisite: A real agent commit/push terminal observation while the diff remains mounted, plus a disposable Git remote.
Exact CLI recipe & bug checks
Expected: a plain workspace holding one dirty repository exposes that repository's changes. Known failure (reproduced 2026-10-16, pinned SDK 1.55.0, OpenHands/software-agent-sdk#5641): the changes request returns200 with no changes and Commits reads No commits yet although the child repository is dirty. The upstream fix is closed but not included in the current pin. The recipe removes only its own verified empty parent .git; never remove a user's repository.
sh known-bug F08.uncommitted-changes nested-repository
control-openhands browser viewport desktop
QA_ID="$(python3 - <<'PY'
import json, os, pathlib, subprocess, tempfile
base = pathlib.Path(os.environ["OH_VERIFY_RUN"]) / "workspace"
base.mkdir(exist_ok=True)
fixture = pathlib.Path(tempfile.mkdtemp(prefix="qa-surface-", dir=base))
llm = {"model":"openai/gpt-4o", "base_url":"http://127.0.0.1:1/v1", "api_key":"qa-not-real", "num_retries":0, "timeout":2}
def create(workspace, parent=None, tags=None):
payload = {"agent":{"kind":"Agent", "llm":llm, "tools":[]}, "workspace":{"working_dir":str(workspace)}, "autotitle":False}
if parent: payload["parent_conversation_id"] = parent
if tags: payload["tags"] = tags
request = fixture / "request.json"
request.write_text(json.dumps(payload))
result = subprocess.run(["control-openhands", "api", "POST", "/api/conversations", "--write", "--data", "@"+str(request), "--pick", "id"], check=True, capture_output=True, text=True)
return json.loads(result.stdout)["value"]
def git(directory, *args):
subprocess.run(["git", "-C", str(directory), *args], check=True, capture_output=True)
import shutil
parent = fixture / "parent"
child = parent / "proj"
child.mkdir(parents=True)
git(child, "init", "-q", "-b", "main")
(child / "README.md").write_text("before\n")
git(child, "add", ".")
git(child, "-c", "user.name=QA", "-c", "user.email=qa@example.invalid", "commit", "-qm", "Owned nested fixture")
(child / "README.md").write_text("after\n")
(child / "notes.txt").write_text("untracked\n")
conversation_id = create(parent)
empty_git = parent / ".git"
if empty_git.exists():
assert empty_git.is_dir() and not empty_git.is_symlink()
top = pathlib.Path(subprocess.check_output(["git", "-C", str(parent), "rev-parse", "--show-toplevel"], text=True).strip())
assert top.resolve() == parent.resolve()
assert not subprocess.check_output(["git", "-C", str(parent), "ls-files"], text=True).strip()
shutil.rmtree(empty_git)
print(conversation_id)
PY
)"
control-openhands browser goto "/conversations/$QA_ID"
control-openhands browser click 'testid=right-panel-toggle'
control-openhands browser click 'testid=conversation-tab-commits'
control-openhands browser wait '#conversation-tab-panel main'
control-openhands browser wait 'testid=commits-tab-status' --state detached --timeout 10000
control-openhands assert --field recent.0.status --equals 200 -- browser network --filter '/git/changes'
control-openhands browser screenshot --feature F08.uncommitted-changes --name nested-repository
control-openhands assert --field count --equals 1 --bug-shows 0 -- browser count 'testid=uncommitted-changes-row' # bug
Family notes, prerequisites for known issues & limits Known issueBlocked
- When the drawer is closed, its tabs stay mounted at width 0.
browser count 'testid=files-tab'is1even then, so assert withbrowser visibleorright-panel-togglearia-pressed. - The tab row is a
tablist "Conversation panels"ofrole=tabcontrols, each named after its tab even when inactive (#17612), sorole=tab[name="Commits"]finds the Commits tab whether or not it is active;role=button[name="Commits"]finds nothing.aria-selectedmarks the selected tab. Theconversation-tab-*test ids still work. - The tab row ends with the ellipsis menu (
Customize tabs), then theVSCodebutton (drawer-vscode-link, namedOpen in VS Code) when the runtime reports a running editor. #17660 removed that button and #18048 restored it, so on a checkout between the two the drawer has no editor control at all. - The overflow menu is not a
role=menu. Escape closes it and returns focus to the tab bar'sellipsis-button, both in the desktop drawer and on the phone/panelpage; choosing a row or an outside click (browser mouse-click) closes it too. - The Files list and the open preview do not follow bash edits:
use-auto-refresh-files-on-edit.tsrefreshes the file queries only for file-editor observations, and its comment says shell observations skip them on purpose (so that every shell command does not reload canvas iframes). This map still expects files the agent writes to appear (F08.files-auto-refresh), so the bash case stays a fail row. Clickfiles-tab-refreshonce before asserting on files the agent made with the shell. Since #18032 that one click also re-reads the open file (the content fetch revalidates instead of using the browser HTTP cache, and the rich preview gets a new?v=), and a page reload shows the current body without a click. - The chat also fetches the file list (to linkify paths). A conversation page left open while the agent works can therefore show a stale tree the moment the drawer first opens.
- Binary detection is a NUL-byte sniff of the first 8 KB, and the Office message also needs that sniff to trip. A 4-byte
PK\003\004"pptx" or random bytes without a NUL render as text in the plain viewer. Fixtures must contain a NUL. control-openhands fixture folderwrites a plain folder withnotes.md, but by the time the conversation has run the folder holds an empty.git, so a conversation that leavesnotes.mdin place showsUncommitted 1 filerather than the empty state. Delete it first (as in the recipe).- Diff views are Monaco.
testid=file-diff-viewerandfile-single-viewerare not on the DOM; read text throughtestid=editor-container. The narrow drawer falls back to an inline (not side-by-side) diff. The view-mode buttons are namedOld version,DiffandNew version, andcollapseis namedExpandorCollapseafter its state (#18033). browser eval "navigator.clipboard.readText()"hangs for about 2 minutes (no clipboard permission). Usecontrol-openhands browser clipboardto read what Copy wrote.browser click ... --observeaccepts the usual selector syntax (testid=...).- Two
.cursor-ew-resizegrips sit in the conversation page (chat/drawer, then the Files tree); scope the tree one withtestid=files-tab-tree-resize-handle. - At phone width, chat path links (and the agent's
canvas_ui_controltab and file requests, throughsrc/services/canvas-ui.ts) navigate to/conversations/<id>/panel(#18045); at desktop width they open the side drawer and the URL does not change. A/panelURL above 1024 px redirects to/conversations/<id>, so a bullet that leaves the phone viewport on/panelends on the chat route. - If the agent's terminal resets mid-command ("terminal session was reset", for example after a
restart), the agent reruns the command. Check the workspace withgit statusthrough a second prompt rather than trusting the first observation. Runs no longer share a tmux server (OpenHands/OpenHands#17946). - Known issue: #17567 (Monaco disposed-model page error on diff unmount; it also fires on collapse and on a diff→old switch).
- Known issue #18108 (
F08.files-tree): Files tab shows "No files in workspace" on cloud when the agent writes outside /workspace/project. Live prerequisite: An authenticated Enterprise Cloud sandbox, no repository selected, with files outside its default working directory. - Known issue #17773 (
F08.files-tree): Files tab always shows "No files in workspace" on Windows local backend. Live prerequisite: A Windows local Agent Server using cmd.exe; this run is macOS. - Known issue #16949 (
F08.files-refresh): Keep Files and Changes views source-driven after refresh. Live prerequisite: An authenticated Cloud repository for the refresh-query mismatch, or real file-editor observations causing an in-place M-to-D Git refetch. - Known issue #16716 (
F08.uncommitted-changes): Diff view is regularly out of sync. Live prerequisite: The original simultaneous-agent/shared-repository state or a deterministic stale-diff trigger; the report only suggests that timing. - Known issue #16689 (
F08.files-load-error): Whenworkspace/paths throw a 401, call the workspace-session endpoint. Live prerequisite: The original fresh-VM URL, auth/cookie state and exact request sequence; the report says the trigger is unclear. - Known issue #16454 (
F08.uncommitted-changes): Diff list is showing duplicate entries. Live prerequisite: The original Git changes response and update sequence producing duplicate rows; the report provides no trigger. - Known issue #16331 (
F08.files-tree): file preview panel stops working during streaming. Live prerequisite: A real streaming/tool-running conversation while selecting a second workspace file. - Known issue #15692 (
F08.uncommitted-changes): Diff still shows after agent has committed and pushed. Live prerequisite: A real agent commit/push terminal observation while the diff remains mounted, plus a disposable Git remote. - Known issue #15567 (
F08.files-load-error): Workspace file preview returns 401 Unauthorized on non-HTTPS non-loopback hosts. Live prerequisite: A plain-HTTP non-loopback deployment and browser accessing that origin, with workspace-cookie observation. - Known issue #15519 (
F08.files-load-error): "files" tab in Files side panel throws 401 Unauthorized. Live prerequisite: The reported Linux Docker deployment/auth topology; this isolated run uses the macOS source launcher. - Known issue #15501 (
F08.files-rich-plain): Generated-page JavaScript was blocked by an iframe sandbox. Files Rich intentionally disables scripts; the report does not identify whether Files or Browser was used. Live prerequisite: The original preview panel and file URL or minimal page identifying the intended surface; Files Rich deliberately blocks scripts, while Browser is separate. - Known issue OpenHands/software-agent-sdk#4562 (
F08.files-load-error): Plain-HTTP non-loopback workspace previews can return401 after the browser rejects the workspace-session cookie. Live prerequisite: A plain-HTTP non-loopback same-origin browser deployment with real workspace-cookie observation. - Known issue #16871 (
F08.vscode-link): the sharedtransformVSCodeUrlstill accepts arbitrary schemes, and the current Clouduse-unified-vscode-url.tsbranch passes sandboxexposed_urlseditor values through it without the Local origin gate. Live prerequisite: an authorized Cloud account/sandbox whose editorexposed_urlsvalue can be controlled for safe URL-scheme rejection checks. Local inspection on 2026-10-12 atfa1f724ab573found no Download via VS Code action in the real card menu: its only production caller does not supply the requiredshowOptionsprop. The current drawer opens withnoopener,noreferrer. Neither the old opener exploit nor the remaining Cloud scheme scenario was reproduced. Evidence:backfill-18309-surface-rechecks/F04.download/vscode-card-action-absent-16871.pngand its ARIA snapshot. Keep this linked to the live editor-link consumer; do not infer the whole issue resolved from the removed card action. - Known issue OpenHands/software-agent-sdk#5845 (
F08.vscode-link): reserved characters in the workspace path can split the editor URL's unencodedfolderparameter and open the wrong folder. Live prerequisite: a supported origin serving a genuinely enabled/running editor and an owned conversation in a path containing&or#. A direct URL-constructor call does not exercise the link; Cloudexposed_urlstakes another path. - Known issue OpenHands/software-agent-sdk#5784 (
F08.files-rich-plain,F08.files-fallbacks,F08.files-open-new-window): a path-prefixed Agent Server can set a workspace cookie whose Path omits the prefix, leaving HTML/image/file previews unauthorized. Live prerequisite: an authenticated path-prefixed owned Local server withOH_WEB_URLand a real cookie-authenticated preview; the ordinary root-path launch cannot exercise that cookie Path.
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.