Engel’s notebook

Launcher modes, Docker, desktop and library

F26 · 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.

F26 Launcher modes, Docker, desktop and library

34 behaviors · 38 scripts & contracts · Maintained source

Entry points & prerequisites

What this family covers

Agent Canvas ships one UI in several runtime shapes. The npm launcher (npx @openhands/agent-canvas, here node bin/agent-canvas.mjs) runs the full stack, or only the frontend or only the backends, prints its version and stack info, refuses conflicting flags, and decides whether the session key is injected into the page (loopback) or the user must paste it (--public, or a non-loopback --host). The same stack also ships as a Docker image, an Electron desktop app and an embeddable React library. Launchers tell the agent which services exist through /server_info.runtime_services and a <RUNTIME_SERVICES> block in every new conversation's system prompt.

How a user gets here

  • A terminal: npx @openhands/agent-canvas [flags]; from this checkout, node bin/agent-canvas.mjs [flags] (what control-openhands launch starts with --port, and --public for public mode).
  • The browser at the ingress URL the launcher prints (http://localhost:8000/ by default).
  • Backend selector (backend-selector) → Add Backend (add-backend-menu-item) → Agent-server → Remote, and Manage Backends to remove it (see F25 for the backend registry itself).
  • Public mode and the API-key screen are mapped in F01 (F01.api-key-entry, F01.onboarding-backend-step).
  • npm run build:lib for the embeddable library; a host app imports @openhands/agent-canvas (see docs/DEVELOPMENT.md, "Cloud organization recovery in embedded hosts").
  • docker run ... ghcr.io/openhands/agent-canvas or npm run build:docker, then http://localhost:8000/canvas.
  • helm install agent-canvas ./helm/agent-canvas on a Kubernetes cluster (helm/agent-canvas/README.md).
  • npm run desktop (development) or a packaged build from npm run build:desktop; the window opens itself.
  • ACP agents (Claude Code, Codex, Gemini CLI) are chosen in onboarding (F01) and Settings → Agents (F13); this family does not repeat them.

Given

Preconditions:

  • Baseline state (launched, doctored, onboard --skip done) on your own run: control-openhands launch --new --build never, export OH_VERIFY_RUN=<run from the launch JSON>, control-openhands doctor, control-openhands onboard --skip. Commands run from the checkout root.
  • F26.desktop-* need the Electron binary (node_modules/electron/dist/electron; npx --no-install electron --version downloads it on first use) and Xvfb plus ImageMagick import for headless capture. The app binds the fixed ports 8000, 18000, 18001 and 3001: check them like the blocks below and keep the run short.
  • F26.runtime-services and F26.runtime-services-agent-use need an active LLM profile (control-openhands llm preset deepseek); the second also needs the automation service running and costs two short runs (the conversation and the dispatched automation run).
  • The partial-stack and LAN bullets start extra launchers by hand (harness gap: launch has no --frontend-only, --backend-only or --host). Each one needs a free block of ports: these recipes use 18960–18963 and 18970–18973, plus 19961, the editor port a launcher that serves VS Code hands its agent-server (see Gotchas); check them first with for p in 18960 18961 18962 18963 18970 18971 18972 18973 19961; do (echo > /dev/tcp/127.0.0.1/$p) 2>/dev/null && echo "$p busy"; done (no output means free). Each launcher gets a private HOME and state under $OH_VERIFY_RUN/private/, so it never touches ~/.openhands or your run.
VersionF26.cli-versionMapped behavior

Scenario F26--version · Permalink · Source

Given

Use F26’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

  1. Do
    Run node bin/agent-canvas.mjs --version; echo "exit=$?" and node bin/agent-canvas.mjs -v; echo "exit=$?". Each prints one line equal to jq -r .version package.json (the checkout's package version, which changes with every release: compare, do not expect a number; e.g. [ "$(node bin/agent-canvas.mjs --version)" = "$(jq -r .version package.json)" ] && echo same) and exit=0.

Expect

--version and -v print the package version and exit 0.

Exact CLI recipe & bug checks

Run node bin/agent-canvas.mjs --version; echo "exit=$?" and node bin/agent-canvas.mjs -v; echo "exit=$?". Each prints one line equal to jq -r .version package.json (the checkout's package version, which changes with every release: compare, do not expect a number; e.g. [ "$(node bin/agent-canvas.mjs --version)" = "$(jq -r .version package.json)" ] && echo same) and exit=0.

InfoF26.cli-infoMapped behavior

Scenario F26--info · Permalink · Source

Given

Use F26’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

  1. Do
    Run node bin/agent-canvas.mjs --info. It prints @openhands/agent-canvas <version> (the --version value), Default stack versions: with agent-server: <pin> and automation: <pin>, Compatibility: agent-server: >= <minimum>, Default ports: ingress: 8000, agent-server: 18000, automation: 18001, and the override variables OH_AGENT_SERVER_VERSION, OH_AGENT_SERVER_GIT_REF, OH_AGENT_SERVER_LOCAL_PATH / OH_AUTOMATION_VERSION, OH_AUTOMATION_GIT_REF. The three versions are read from config/defaults.json (versions.agentServer, versions.automation, compatibility.minimumAgentServer) and move with every pin bump, so compare them with the file instead of expecting numbers: test "$(node bin/agent-canvas.mjs --info | awk '/^(Default stack versions|Compatibility):/{s=1;next} /^$/{s=0} s{print $NF}' | paste -sd' ' -)" = "$(jq -r '[.versions.agentServer,.versions.automation,.compatibility.minimumAgentServer]|join(" ")' config/defaults.json)" && echo match || echo mismatch prints match, and printf '%s\n' "$(jq -r .compatibility.minimumAgentServer config/defaults.json)" "$(jq -r .versions.agentServer config/defaults.json)" | sort -VC && echo pin-meets-minimum || echo below-minimum prints pin-meets-minimum (the default Agent Server passes the UI's own version gate).

Expect

--info prints the package version, the default agent-server and automation pins, the minimum agent-server version, the default ports and the override env vars.

Exact CLI recipe & bug checks

Run node bin/agent-canvas.mjs --info. It prints @openhands/agent-canvas <version> (the --version value), Default stack versions: with agent-server: <pin> and automation: <pin>, Compatibility: agent-server: >= <minimum>, Default ports: ingress: 8000, agent-server: 18000, automation: 18001, and the override variables OH_AGENT_SERVER_VERSION, OH_AGENT_SERVER_GIT_REF, OH_AGENT_SERVER_LOCAL_PATH / OH_AUTOMATION_VERSION, OH_AUTOMATION_GIT_REF. The three versions are read from config/defaults.json (versions.agentServer, versions.automation, compatibility.minimumAgentServer) and move with every pin bump, so compare them with the file instead of expecting numbers: test "$(node bin/agent-canvas.mjs --info | awk '/^(Default stack versions|Compatibility):/{s=1;next} /^$/{s=0} s{print $NF}' | paste -sd' ' -)" = "$(jq -r '[.versions.agentServer,.versions.automation,.compatibility.minimumAgentServer]|join(" ")' config/defaults.json)" && echo match || echo mismatch prints match, and printf '%s\n' "$(jq -r .compatibility.minimumAgentServer config/defaults.json)" "$(jq -r .versions.agentServer config/defaults.json)" | sort -VC && echo pin-meets-minimum || echo below-minimum prints pin-meets-minimum (the default Agent Server passes the UI's own version gate).

HelpF26.cli-helpMapped behavior

Scenario F26--help · Permalink · Source

Given

Use F26’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

  1. Do
    Run node bin/agent-canvas.mjs --help. It prints USAGE:, AUTH MODES: (--public ... Users must paste it when the UI loads.), OPTIONS: (-p, --port, -H, --host, --public, --frontend-only, --backend-only, -v, --version, --info, -h, --help), ENVIRONMENT VARIABLES: and EXAMPLES:, exit 0.

Expect

--help prints usage, auth modes, options, env vars and examples.

Exact CLI recipe & bug checks

Run node bin/agent-canvas.mjs --help. It prints USAGE:, AUTH MODES: (--public ... Users must paste it when the UI loads.), OPTIONS: (-p, --port, -H, --host, --public, --frontend-only, --backend-only, -v, --version, --info, -h, --help), ENVIRONMENT VARIABLES: and EXAMPLES:, exit 0.

Conflicting flagsF26.cli-flag-conflictsMapped behavior

Scenario F26--conflicting-flags · Permalink · Source

Given

Use F26’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

  1. Do
    Run node bin/agent-canvas.mjs --frontend-only --backend-only; echo "exit=$?" → Error: --frontend-only and --backend-only cannot be used together, exit=1. Run node bin/agent-canvas.mjs --public --frontend-only; echo "exit=$?" → Error: --public cannot be used with --frontend-only, exit=1.

Expect

--frontend-only with --backend-only, and --public with --frontend-only, exit 1 with a one-line error before anything starts.

Exact CLI recipe & bug checks

Run node bin/agent-canvas.mjs --frontend-only --backend-only; echo "exit=$?" → Error: --frontend-only and --backend-only cannot be used together, exit=1. Run node bin/agent-canvas.mjs --public --frontend-only; echo "exit=$?" → Error: --public cannot be used with --frontend-only, exit=1.

Public mode needs a keyF26.cli-public-needs-keyKnown issue

Scenario F26--public-mode-needs-a-key · Permalink · Source

Given

Use F26’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

  1. Do
    Launch an isolated stack.

Expect

--public without LOCAL_BACKEND_API_KEY exits 1 with guidance.

Linked known issues

Known issue OpenHands/software-agent-sdk#5731 (F26.cli-public-needs-key): whitespace-only session keys remain configured credentials in the Agent Server. The Canvas launcher forwards a nonempty LOCAL_BACKEND_API_KEY verbatim, so a whitespace-only deployment value can leave the public login unable to use its intended key. Recheck an owned loopback-only public deployment with a whitespace-only supplied key, its actual HTTP/WebSocket authentication and a normal nonblank control; never test the server's default broad bind on an exposed interface. The SDK's missing-OH_SECRET_KEY cipher fallback is a separate configuration branch from a Canvas launcher that supplies an encryption key.

Exact CLI recipe & bug checks

Run T=$(mktemp -d); env -u LOCAL_BACKEND_API_KEY HOME=$T OH_CANVAS_SAFE_STATE_DIR=$T/state timeout 60 node bin/agent-canvas.mjs --public --port 18990; echo "exit=$?"; rm -rf $T. After ✓ uvx found it prints ✗ PUBLIC MODE requires LOCAL_BACKEND_API_KEY environment variable. and exit=1. (The successful --public launch is control-openhands launch --new --public, mapped in F01.)

Missing buildF26.cli-missing-buildMapped behavior

Scenario F26--missing-build · Permalink · Source

Given

Use F26’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

  1. Do
    Never move this checkout's build/ (other runs serve it); copy the launcher instead: T=$(mktemp -d); mkdir -p $T/bin $T/config; cp bin/agent-canvas.mjs $T/bin/; cp package.json $T/; cp config/defaults.json $T/config/; node $T/bin/agent-canvas.mjs --port 18990; echo "exit=$?"; rm -rf $T. It prints Error: No build found at <T>/build, the npm install / npm run build hint and exit=1.

Expect

a package without build/ exits 1 with No build found and build instructions.

Exact CLI recipe & bug checks

Never move this checkout's build/ (other runs serve it); copy the launcher instead: T=$(mktemp -d); mkdir -p $T/bin $T/config; cp bin/agent-canvas.mjs $T/bin/; cp package.json $T/; cp config/defaults.json $T/config/; node $T/bin/agent-canvas.mjs --port 18990; echo "exit=$?"; rm -rf $T. It prints Error: No build found at <T>/build, the npm install / npm run build hint and exit=1.

Port in useF26.cli-port-in-useMapped behavior

Scenario F26--port-in-use · Permalink · Source

Given

Use F26’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

  1. Do
    Point a second launcher at your run's ingress port: P=$(control-openhands status | jq -r .ports.ingress); T=$(mktemp -d); HOME=$T OH_CANVAS_SAFE_STATE_DIR=$T/state timeout 60 node bin/agent-canvas.mjs --port $P; echo "exit=$?"; rm -rf $T. It prints Cannot start: the following ports are already in use: with • ingress: port <P> and Another agent-canvas instance may already be running., then exits 1. Your run is untouched (control-openhands doctor stays ok).

Expect

an occupied ingress (or service) port stops the launch with Cannot start: the following ports are already in use naming each busy port.

Exact CLI recipe & bug checks

Point a second launcher at your run's ingress port: P=$(control-openhands status | jq -r .ports.ingress); T=$(mktemp -d); HOME=$T OH_CANVAS_SAFE_STATE_DIR=$T/state timeout 60 node bin/agent-canvas.mjs --port $P; echo "exit=$?"; rm -rf $T. It prints Cannot start: the following ports are already in use: with • ingress: port <P> and Another agent-canvas instance may already be running., then exits 1. Your run is untouched (control-openhands doctor stays ok).

Loopback-only ingressF26.loopback-bind-defaultNot run

Scenario F26--loopback-only-ingress · Permalink · Source

Given

Use F26’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

  1. Do
    Your run was started without --host. Run P=$(control-openhands status | jq -r .ports.ingress); curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:$P/ (200), then find the machine's own address portably, LAN=$(node -e 'for (const a of Object.values(require("os").networkInterfaces()).flat()) if (a.family === "IPv4" && !a.internal) { console.log(a.address); break }'); echo "LAN=$LAN", and curl -s -o /dev/null -w '%{http_code}\n' --connect-timeout 3 http://$LAN:$P/; echo "exit=$?". At that address curl prints 000 and exit=7 (connection refused). If LAN is empty the machine has no non-loopback address: record the loopback half and leave the refused-connection half not-run with that reason (it is the behavior this ID asserts, so an empty LAN is never a pass). Read-only second view, Linux form: awk -v p=$(printf '%04X' $P) 'NR>1 && $4=="0A" && $2 ~ ":"p"$" {print $2}' /proc/net/tcp prints only 0100007F:<hex port> (127.0.0.1 in /proc/net/tcp's byte order; elsewhere list the listener with the OS's socket tool, for example ss -ltn 'sport = :'$P or lsof -nP -iTCP:$P -sTCP:LISTEN, and expect 127.0.0.1:<P> only), and grep -a 'Listening on' $OH_VERIFY_RUN/private/stack.log shows the ingress banner Listening on: http://localhost:<P>/. The LAN half of F26.host-bind-lan below is the contrast: there the same curl answers 200.

Expect

without --host the ingress listens on 127.0.0.1 only: the machine's own non-loopback address refuses the connection while loopback serves the app.

Exact CLI recipe & bug checks

Your run was started without --host. Run P=$(control-openhands status | jq -r .ports.ingress); curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:$P/ (200), then find the machine's own address portably, LAN=$(node -e 'for (const a of Object.values(require("os").networkInterfaces()).flat()) if (a.family === "IPv4" && !a.internal) { console.log(a.address); break }'); echo "LAN=$LAN", and curl -s -o /dev/null -w '%{http_code}\n' --connect-timeout 3 http://$LAN:$P/; echo "exit=$?". At that address curl prints 000 and exit=7 (connection refused). If LAN is empty the machine has no non-loopback address: record the loopback half and leave the refused-connection half not-run with that reason (it is the behavior this ID asserts, so an empty LAN is never a pass). Read-only second view, Linux form: awk -v p=$(printf '%04X' $P) 'NR>1 && $4=="0A" && $2 ~ ":"p"$" {print $2}' /proc/net/tcp prints only 0100007F:<hex port> (127.0.0.1 in /proc/net/tcp's byte order; elsewhere list the listener with the OS's socket tool, for example ss -ltn 'sport = :'$P or lsof -nP -iTCP:$P -sTCP:LISTEN, and expect 127.0.0.1:<P> only), and grep -a 'Listening on' $OH_VERIFY_RUN/private/stack.log shows the ingress banner Listening on: http://localhost:<P>/. The LAN half of F26.host-bind-lan below is the contrast: there the same curl answers 200.

Backend-onlyF26.backend-onlyMapped behavior

Scenario F26--backend-only · Permalink · Source

Given

Use F26’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

  1. Do
    Start it: Q=$OH_VERIFY_RUN/private/f26-backend; mkdir -p $Q; HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state OH_CANVAS_SAFE_BACKEND_PORT=18961 OH_CANVAS_SAFE_AUTOMATION_PORT=18962 OH_CANVAS_SAFE_VITE_PORT=18963 LOCAL_BACKEND_API_KEY=qa-f26-backend-key OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 nohup node bin/agent-canvas.mjs --backend-only --port 18960 > $Q/launcher.log 2>&1 & echo $! > $Q/pid. Wait until curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:18960/health is 200 (30–90 s; the first start downloads the agent-server into the private HOME). Then curl -s http://127.0.0.1:18960/ prints No backend configured for this route (status 503, also for /index.html and /assets/x.js), /server_info is 200, /api/settings is 401 without a key and 200 with -H 'X-Session-API-Key: qa-f26-backend-key'. Keep it running for the next two bullets.

Expect

--backend-only serves the APIs (key-protected) and answers 503 No backend configured for this route for / and static assets.

Exact CLI recipe & bug checks

Start it: Q=$OH_VERIFY_RUN/private/f26-backend; mkdir -p $Q; HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state OH_CANVAS_SAFE_BACKEND_PORT=18961 OH_CANVAS_SAFE_AUTOMATION_PORT=18962 OH_CANVAS_SAFE_VITE_PORT=18963 LOCAL_BACKEND_API_KEY=qa-f26-backend-key OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 nohup node bin/agent-canvas.mjs --backend-only --port 18960 > $Q/launcher.log 2>&1 & echo $! > $Q/pid. Wait until curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:18960/health is 200 (30–90 s; the first start downloads the agent-server into the private HOME). Then curl -s http://127.0.0.1:18960/ prints No backend configured for this route (status 503, also for /index.html and /assets/x.js), /server_info is 200, /api/settings is 401 without a key and 200 with -H 'X-Session-API-Key: qa-f26-backend-key'. Keep it running for the next two bullets.

Frontend-onlyF26.frontend-onlyMapped behavior

Scenario F26--frontend-only · Permalink · Source

Given

Use F26’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

  1. Do
    Open http://127.0.0.1:18970/.

Expect

--frontend-only serves the SPA; /server_info, /api/*, /sockets/* and /api/automation/* answer 503, and a browser new to that origin opens on the Add a backend onboarding step without an error toast.

Exact CLI recipe & bug checks

Start it: Q=$OH_VERIFY_RUN/private/f26-frontend; mkdir -p $Q; HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state OH_CANVAS_SAFE_BACKEND_PORT=18971 OH_CANVAS_SAFE_AUTOMATION_PORT=18972 OH_CANVAS_SAFE_VITE_PORT=18973 DO_NOT_TRACK=1 nohup node bin/agent-canvas.mjs --frontend-only --port 18970 > $Q/launcher.log 2>&1 & echo $! > $Q/pid (ready in a few seconds). for u in / /server_info /api/settings /sockets/events/x /api/automation/v1 /vscode/; do curl -s -o /dev/null -w "%{http_code} $u\n" http://127.0.0.1:18970$u; done prints 200 / and 503 for the four backend paths and for /vscode/: the launcher serves the VS Code editor and reserves its path, and grep -a 'vscode -> 503' $Q/launcher.log prints [static] /vscode -> 503 (rejected) (see Gotchas). In the browser: control-openhands browser goto http://127.0.0.1:18970/ --allow-external, control-openhands browser value 'testid=onboarding-backend-name' (Local), control-openhands browser value 'testid=onboarding-backend-host' (http://127.0.0.1:18970, the page origin), sleep 12, control-openhands browser toasts --history ([]: no error toast) and control-openhands browser screenshot --feature F26.frontend-only --name add-backend-step (the Add a backend card).

Cross-connectF26.cross-connectMapped behavior

Scenario F26--cross-connect · Permalink · Source

Given

Use F26’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

  1. Do
    Enter “QA Backend Only” in “Onboarding backend name”.
  2. Do
    Enter “http://127.0.0.1:18960” in “Onboarding backend host”.
  3. Do
    Enter “qa-f26-backend-key” in “Onboarding backend api key”.
  4. Do
    Choose “Onboarding backend next”.
  5. Wait
    Wait for “Onboarding step choose agent” to appear.
  6. Wait
    Wait for “Telemetry consent form” to appear.
  7. Do
    Use browser uncheck as specified in the exact recipe.
  8. Do
    Choose “Confirm telemetry preferences”.
  9. Do
    Choose “Onboarding skip”.
  10. Wait
    Wait for “Root layout” to appear.
  11. Do
    Reload the page.
  12. Do
    Open http://127.0.0.1:18970/settings/secrets.

Expect

a frontend-only UI connects to a separate backend-only instance through Add a backend; the shell then shows that backend as Connected and all API traffic goes to it.

Exact CLI recipe & bug checks

On that page run control-openhands browser fill 'testid=onboarding-backend-name' 'QA Backend Only', control-openhands browser fill 'testid=onboarding-backend-host' 'http://127.0.0.1:18960', control-openhands browser fill 'testid=onboarding-backend-api-key' qa-f26-backend-key, control-openhands browser click 'testid=onboarding-backend-next' and control-openhands browser wait 'testid=onboarding-step-choose-agent' --timeout 20000. On a backend state that has never answered telemetry consent, the consent dialog opens on top (control-openhands browser wait 'testid=telemetry-consent-form' --timeout 10000 succeeds; it appears a second or two after the step, so an immediate browser count can still read 0; consent is stored on the backend, so a second pass with the same $Q skips it): control-openhands browser uncheck 'testid=telemetry-consent-form >> role=checkbox', control-openhands browser click 'testid=confirm-telemetry-preferences', then control-openhands browser click 'testid=onboarding-skip' and control-openhands browser wait 'testid=root-layout' --timeout 20000. After control-openhands browser reload, control-openhands browser snapshot 'testid=backend-selector' shows status "Connected" and combobox "QA Backend Only". control-openhands browser goto http://127.0.0.1:18970/settings/secrets --allow-external and control-openhands browser count 'testid=secret-item >> has-text=OPENHANDS_AUTOMATION_API_KEY' is 1 (the backend-only instance's seeded secret), and control-openhands browser network --filter 18960 --last 5 shows byOrigin with only http://127.0.0.1:18960. control-openhands browser errors --app-only has pageErrors 0.

Remote backend from a normal CanvasF26.remote-backendKnown issue

Scenario F26--remote-backend-from-a-normal-canvas · Permalink · Source

Given

Use F26’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

  1. Do
    Open /.
  2. Do
    Choose “Backend selector”.
  3. Do
    Choose “Add backend menu item”.
  4. Do
    Choose “Add backend option agent server”.
  5. Do
    Choose “Add backend location option remote”.
  6. Wait
    Wait for “Add backend modal” to disappear.
  7. Do
    Reload the page.
  8. Wait
    Wait for “Telemetry consent form” to appear.
  9. Do
    Use browser uncheck as specified in the exact recipe.
  10. Do
    Choose “Confirm telemetry preferences”.
  11. Do
    Choose “Backend selector”.
  12. Do
    Choose “Local”.
  13. Wait
    Wait for “Backend selector” / “Local” to appear.
  14. Do
    Reload the page.

Expect

from a normal Canvas, backend selector → Add Backend → Agent-server → Remote shows the self-hosting guidance and connects to another instance (stand-in for a self-hosted VM, docs/SELF_HOSTING.md).

Linked known issues

Known issue #15434 (F26.remote-backend): The embedded VS Code action on a self-hosted reverse-proxy origin was reported to reopen Canvas because its advertised URL omitted the separate editor port/path. Current launchers include an ingress /vscode/ route; recheck against the affected HTTPS proxy deployment with an actually available editor binary and emitted URL before concluding resolution. This run lacks that self-hosted proxy/editor deployment.

Known issue OpenHands/software-agent-sdk#5373 (F26.remote-backend): A self-hosted Agent Server using default Rich traceback logging can wedge its event loop and grow memory when a websocket error has a cyclic/deep exception chain; Canvas then times out or disconnects. The npm launcher forces JSON logging, so rechecking needs a separately hosted default-Rich backend plus the reported pathological peer/backlog reproducer and bounded memory/time isolation. That original peer/driver is not included in the issue body; do not synthesize a server patch or stress the shared host and claim a live UI reproduction.

Exact CLI recipe & bug checks

Needs the backend-only instance. Run control-openhands browser goto /, control-openhands browser click 'testid=backend-selector', control-openhands browser click 'testid=add-backend-menu-item', control-openhands browser click 'testid=add-backend-option-agent-server' and control-openhands browser click 'testid=add-backend-location-option-remote'. control-openhands browser text 'testid=add-backend-agent-server-panel' includes Run the remote backend with --public, set a strong LOCAL_BACKEND_API_KEY, and expose it through an SSH tunnel, ngrok, or a TLS reverse proxy. Fill testid=add-backend-name with QA_Remote, testid=add-backend-host with http://127.0.0.1:18960 and testid=add-backend-api-key with qa-f26-backend-key, click testid=add-backend-submit, then control-openhands browser wait 'testid=add-backend-modal' --state detached --timeout 15000, control-openhands browser reload and control-openhands browser snapshot 'testid=backend-selector': status "Connected", combobox "QA_Remote" (the new backend becomes active). If you run this bullet without Cross-connect first, the backend-only state has not answered telemetry consent yet: the consent dialog (This preference is saved for the local backend “QA_Remote” at http://127.0.0.1:18960.) opens over the page and blocks the selector; answer it as in Cross-connect (control-openhands browser wait 'testid=telemetry-consent-form' --timeout 10000, control-openhands browser uncheck 'testid=telemetry-consent-form >> role=checkbox', control-openhands browser click 'testid=confirm-telemetry-preferences'). Switch back with control-openhands browser click 'testid=backend-selector' and control-openhands browser click 'role=option[name="Local"]', then control-openhands browser wait 'testid=backend-selector >> role=combobox[name="Local"]' --timeout 5000 (the switch applies after a short delay: a snapshot taken right after the click still reads combobox "QA_Remote": Local; after the wait it shows combobox "Local"). Clean up: backend-selector → testid=manage-backends-menu-item; control-openhands browser text 'testid=manage-backends-row-QA_Remote' reads QA_Remote, v<version>, http://127.0.0.1:18960, Connected, LOCAL, where <version> is the backend-only instance's own report, curl -s http://127.0.0.1:18960/server_info | jq -r .version (the versions.agentServer pin in config/defaults.json unless your shell exports an OH_AGENT_SERVER_* override, which a hand-started launcher inherits); it passes the UI's gate: printf '%s\n' "$(jq -r .compatibility.minimumAgentServer config/defaults.json)" "$(curl -s http://127.0.0.1:18960/server_info | jq -r .version)" | sort -VC && echo meets-minimum || echo below-minimum prints meets-minimum. Then click testid=manage-backends-remove-QA_Remote, testid=confirmation-modal >> testid=confirm-button, testid=manage-backends-done, browser reload.

Seeded Local backendF26.seeded-local-backendMapped behavior

Scenario F26--seeded-local-backend · Permalink · Source

Given

Use F26’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

  1. Do
    On your run, control-openhands browser eval "JSON.parse(localStorage.getItem('openhands-backends')).map(b=>({id:b.id,name:b.name,host:b.host,kind:b.kind,hasKey:!!b.apiKey}))" returns exactly one entry {id: "default-local", name: "Local", host: "http://127.0.0.1:<ingress>", kind: "local", hasKey: true} (after the Remote cleanup above), and control-openhands browser eval "({key: Boolean(window.__AGENT_CANVAS_SESSION_API_KEY__), authRequired: window.__AGENT_CANVAS_AUTH_REQUIRED__ === true})" is {key: true, authRequired: false}.

Expect

the first load of a launcher-served UI seeds one backend Local (id default-local) with the page origin and the injected key.

Exact CLI recipe & bug checks

On your run, control-openhands browser eval "JSON.parse(localStorage.getItem('openhands-backends')).map(b=>({id:b.id,name:b.name,host:b.host,kind:b.kind,hasKey:!!b.apiKey}))" returns exactly one entry {id: "default-local", name: "Local", host: "http://127.0.0.1:<ingress>", kind: "local", hasKey: true} (after the Remote cleanup above), and control-openhands browser eval "({key: Boolean(window.__AGENT_CANVAS_SESSION_API_KEY__), authRequired: window.__AGENT_CANVAS_AUTH_REQUIRED__ === true})" is {key: true, authRequired: false}.

Rotated session keyF26.session-key-rotatedMapped behavior

Scenario F26--rotated-session-key · Permalink · Source

Given

Use F26’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

  1. Do
    Restart the run-owned stack as specified.
  2. Do
    Reload the page.
  3. Wait
    Wait for “Home screen” to appear.

Expect

in local mode a stale session key left in the browser (the launcher's key changed since the last visit) is replaced by the injected key on load: the app opens on Home without the API-key screen, and the stored Local backend holds the new key (public mode asks for it instead: F01's api-key-entry row).

Exact CLI recipe & bug checks

Still on your run's /, control-openhands browser eval "JSON.parse(localStorage.getItem('openhands-backends'))[0].apiKey === window.__AGENT_CANVAS_SESSION_API_KEY__" is true (compare keys inside the page; never print one). Run md5sum $OH_VERIFY_RUN/private/session-key | cut -c1-12, then control-openhands restart --rotate-key (rotatedKey true: the launcher comes back injecting a new key) and the md5sum line again; the hash changed. The open page still holds the old key, so its polls get 401 until it reloads: run control-openhands browser errors --clear, then control-openhands browser reload and control-openhands browser wait 'testid=home-screen' --timeout 20000. control-openhands browser count is 1 for testid=root-layout and 0 for testid=api-key-entry-screen, testid=agent-server-onboarding-screen and testid=onboarding-modal: Home, not the key prompt public mode shows in the same state (F01.api-key-entry). Second view: control-openhands browser eval "JSON.parse(localStorage.getItem('openhands-backends')).map(b=>({id:b.id,host:b.host,keyMatchesPage:b.apiKey===window.__AGENT_CANVAS_SESSION_API_KEY__}))" is [{id: "default-local", host: "http://127.0.0.1:<ingress>", keyMatchesPage: true}] against the new page key, control-openhands browser eval "JSON.parse(localStorage.getItem('openhands-agent-server-config')).sessionApiKey === window.__AGENT_CANVAS_SESSION_API_KEY__" is true (the legacy key is overwritten too), control-openhands browser network --filter 'api/settings' --last 2 shows status 200 under recent, and control-openhands browser errors --app-only has pageErrors 0 and appErrors 0. control-openhands api GET /api/settings works with the rotated key, and control-openhands browser screenshot --feature F26.session-key-rotated --name home-after-rotation shows Home.

Managed Agents bridgeF26.mars-web-bridgeKnown failure

Scenario F26--managed-agents-bridge · Permalink · Source

Given

Use F26’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

  1. Do
    With both partial stacks up: curl -s "$(control-openhands status | jq -r .baseUrl)/mars/health", curl -s http://127.0.0.1:18960/mars/health and curl -s http://127.0.0.1:18970/mars/health each print {"ok":true,"authRequired":true}, while curl -s "http://127.0.0.1:$(control-openhands status | jq -r .ports.frontend)/mars/health" | head -c 15 prints <!DOCTYPE html> (the static child behind your run's ingress has no second bridge). curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:18960/mars/rpc/getAuthState is 401 (no key). The frontend-only page has the bridge but no key to use it: after the cross-connect, control-openhands browser eval "({marsBridge: !!window.marsBridge, key: Boolean(window.__AGENT_CANVAS_SESSION_API_KEY__)})" on http://127.0.0.1:18970/ is {marsBridge: true, key: false} (what that does to DigitalOcean sign-in is F25's F25.digitalocean-sign-in known failure).

Expect

every launcher-served web stack hosts the DigitalOcean Managed Agents bridge on its ingress by default (MARS_WEB=0 opts out): /mars/health answers {"ok":true,"authRequired":true} on a full, --backend-only, --frontend-only and --host 0.0.0.0 launcher, /mars/rpc/* answers 401 without the session key and works with it, the static child behind the ingress hosts none, and the Electron stack hosts none either (its preload provides window.marsBridge).

Known bug observations

known failure).

Exact CLI recipe & bug checks

With both partial stacks up: curl -s "$(control-openhands status | jq -r .baseUrl)/mars/health", curl -s http://127.0.0.1:18960/mars/health and curl -s http://127.0.0.1:18970/mars/health each print {"ok":true,"authRequired":true}, while curl -s "http://127.0.0.1:$(control-openhands status | jq -r .ports.frontend)/mars/health" | head -c 15 prints <!DOCTYPE html> (the static child behind your run's ingress has no second bridge). curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:18960/mars/rpc/getAuthState is 401 (no key). The frontend-only page has the bridge but no key to use it: after the cross-connect, control-openhands browser eval "({marsBridge: !!window.marsBridge, key: Boolean(window.__AGENT_CANVAS_SESSION_API_KEY__)})" on http://127.0.0.1:18970/ is {marsBridge: true, key: false} (what that does to DigitalOcean sign-in is F25's F25.digitalocean-sign-in known failure).

Stop the partial stacksF26 · setup / cleanupSupport step

Scenario F26--stop-the-partial-stacks · Permalink · Source

Given

Use F26’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

  1. Do
    kill -TERM $(cat $OH_VERIFY_RUN/private/f26-frontend/pid) $(cat $OH_VERIFY_RUN/private/f26-backend/pid); after about 5 s the ports 18960–18963 and 18970–18973 are free again (check as in Preconditions). Kill by the saved pid only, never by pattern.

Expect

Complete this support step before continuing to the next scenario.

Exact CLI recipe & bug checks

kill -TERM $(cat $OH_VERIFY_RUN/private/f26-frontend/pid) $(cat $OH_VERIFY_RUN/private/f26-backend/pid); after about 5 s the ports 18960–18963 and 18970–18973 are free again (check as in Preconditions). Kill by the saved pid only, never by pattern.

LAN bindF26.host-bind-lanNot run

Scenario F26--lan-bind · Permalink · Source

Given

Use F26’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

  1. Do
    Start with a fresh browser profile.
  2. Do
    Open http://127.0.0.1:18960/.
  3. Do
    Choose “Onboarding skip”.
  4. Wait
    Wait for “Api key entry screen” to appear.
  5. Wait
    Wait for “Root layout” to appear.

Expect

--host 0.0.0.0 warns, listens on the machine's LAN address, and serves an index.html that carries __AGENT_CANVAS_AUTH_REQUIRED__ and neither __AGENT_CANVAS_SESSION_API_KEY__ nor the key itself (the loopback launcher's page carries the key marker); the UI behaves like --public (Add a backend with Next disabled; Skip leads to the API-key screen).

Exact CLI recipe & bug checks

Start a full stack on all interfaces: Q=$OH_VERIFY_RUN/private/f26-lan; mkdir -p $Q; HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state OH_CANVAS_SAFE_BACKEND_PORT=18961 OH_CANVAS_SAFE_AUTOMATION_PORT=18962 OH_CANVAS_SAFE_VITE_PORT=18963 LOCAL_BACKEND_API_KEY=qa-f26-lan-key OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 nohup node bin/agent-canvas.mjs --host 0.0.0.0 --port 18960 > $Q/launcher.log 2>&1 & echo $! > $Q/pid, and wait until curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:18960/ is 200. grep -a 'not loopback' $Q/launcher.log shows [auth] Bind host 0.0.0.0 is not loopback — session key will not be injected into HTML and [static] WARNING: bind host 0.0.0.0 is not loopback; refusing to inject the session API key into HTML. The served page proves it: curl -s http://127.0.0.1:18960/ | grep -o '__AGENT_CANVAS_\(SESSION_API_KEY\|AUTH_REQUIRED\)__' | sort -u prints only __AGENT_CANVAS_AUTH_REQUIRED__ and curl -s http://127.0.0.1:18960/ | grep -c qa-f26-lan-key prints 0, while your loopback run's page, curl -s "$(control-openhands status | jq -r .baseUrl)/" | grep -o '__AGENT_CANVAS_\(SESSION_API_KEY\|AUTH_REQUIRED\)__' | sort -u, prints only __AGENT_CANVAS_SESSION_API_KEY__. The bind is real: with LAN from the loopback bullet, curl -s -o /dev/null -w '%{http_code}\n' --connect-timeout 3 http://$LAN:18960/ is 200 here (refused on your run, F26.loopback-bind-default; with an empty LAN this check is not-run). The browser part below expects a profile that has never visited 127.0.0.1:18960 (true in this file's order; on a repeat pass run control-openhands browser reset first and restore your run afterwards, see Gotchas). Then control-openhands browser goto http://127.0.0.1:18960/ --allow-external and control-openhands browser eval "({key: Boolean(window.__AGENT_CANVAS_SESSION_API_KEY__), authRequired: window.__AGENT_CANVAS_AUTH_REQUIRED__ === true})" → {key: false, authRequired: true}. The onboarding shows testid=onboarding-step-check-backend with control-openhands browser enabled 'testid=onboarding-backend-next' false (no key typed). control-openhands browser click 'testid=onboarding-skip' and control-openhands browser wait 'testid=api-key-entry-screen' --timeout 15000 succeed; after sleep 12, control-openhands browser toasts --history is [] (no error toast on either screen); the bridge is mounted behind the key (F26.mars-web-bridge): curl -s http://127.0.0.1:18960/mars/health is {"ok":true,"authRequired":true}, and POST /mars/rpc/getAuthState with -H 'Content-Type: application/json' -d '{"args":[]}' is 401 without and 200 with -H 'X-Session-API-Key: qa-f26-lan-key'; control-openhands browser screenshot --feature F26.host-bind-lan --name api-key-screen. Fill testid=api-key-entry-name with QA_LAN and testid=api-key-entry-api-key with qa-f26-lan-key, click testid=api-key-entry-submit; control-openhands browser wait 'testid=root-layout' --timeout 10000 succeeds. A few seconds later the telemetry consent dialog for the new QA_LAN backend opens and blocks clicks; answer it (role=button[name="Confirm preferences"]) before driving that page further. Stop it with kill -TERM $(cat $Q/pid).

LAN opt-inF26.host-bind-lan-optinKnown failure

Scenario F26--lan-opt-in · Permalink · Source

Given

Use F26’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

  1. Do
    Expected: the opt-in the warning names (Pass --host 127.0.0.1 (default) for local mode, or --allow-lan-session-key only if you accept LAN exposure.) makes the npm launcher inject the key again. Start the LAN stack as above with --host 0.0.0.0 --allow-lan-session-key --port 18960 (log to $Q/launcher2.log), wait for 200, then curl -s http://127.0.0.1:18960/ | grep -o '__AGENT_CANVAS_\(SESSION_API_KEY\|AUTH_REQUIRED\)__' | sort -u. Today it prints only __AGENT_CANVAS_AUTH_REQUIRED__ and the log repeats both "not loopback" warnings: the launcher ignores the flag (fail; see Gotchas). Stop it with kill -TERM $(cat $Q/pid).

Expect

the opt-in the launcher's warning recommends (--allow-lan-session-key) should restore key injection on a LAN bind.

Known bug observations

Known failure (reproduced 2026-10-08): the npm launcher ignores the flag and the page still carries only __AGENT_CANVAS_AUTH_REQUIRED__; #17949.

Exact CLI recipe & bug checks

Expected: the opt-in the warning names (Pass --host 127.0.0.1 (default) for local mode, or --allow-lan-session-key only if you accept LAN exposure.) makes the npm launcher inject the key again. Start the LAN stack as above with --host 0.0.0.0 --allow-lan-session-key --port 18960 (log to $Q/launcher2.log), wait for 200, then curl -s http://127.0.0.1:18960/ | grep -o '__AGENT_CANVAS_\(SESSION_API_KEY\|AUTH_REQUIRED\)__' | sort -u. Today it prints only __AGENT_CANVAS_AUTH_REQUIRED__ and the log repeats both "not loopback" warnings: the launcher ignores the flag (fail; see Gotchas). Stop it with kill -TERM $(cat $Q/pid).

Generated session key persistsF26.session-key-persistMapped behavior

Scenario F26--generated-session-key-persists · Permalink · Source

Given

Use F26’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

  1. Do
    With 18960–18963 free, define a helper and start a loopback stack without a key: Q=$OH_VERIFY_RUN/private/f26-keys; mkdir -p $Q; start(){ HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state OH_CANVAS_SAFE_BACKEND_PORT=18961 OH_CANVAS_SAFE_AUTOMATION_PORT=18962 OH_CANVAS_SAFE_VITE_PORT=18963 OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 "$@" > $Q/launcher.log 2>&1 & echo $! > $Q/pid; for i in $(seq 1 60); do c=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:18960/); [ "$c" = 200 ] && break; sleep 3; done; echo "code=$c"; }; start env -u LOCAL_BACKEND_API_KEY nohup node bin/agent-canvas.mjs --port 18960 (code=200). curl -s http://127.0.0.1:18960/ | grep -o '__AGENT_CANVAS_SESSION_API_KEY__[^;<]*' | md5sum prints a hash (never print the key itself); $Q/home/.openhands/agent-canvas/api-key.txt exists (64 hex characters) and curl -s -o /dev/null -w '%{http_code}' -H "X-Session-API-Key: $(cat $Q/home/.openhands/agent-canvas/api-key.txt)" http://127.0.0.1:18960/api/settings is 200. kill -TERM $(cat $Q/pid); sleep 6, run the same start ... line again in the same shell and repeat the md5sum line: the hash is identical.

Expect

in local mode without LOCAL_BACKEND_API_KEY the launcher generates a session key, saves it under $HOME/.openhands/agent-canvas/api-key.txt and injects the same key again on the next start.

Exact CLI recipe & bug checks

With 18960–18963 free, define a helper and start a loopback stack without a key: Q=$OH_VERIFY_RUN/private/f26-keys; mkdir -p $Q; start(){ HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state OH_CANVAS_SAFE_BACKEND_PORT=18961 OH_CANVAS_SAFE_AUTOMATION_PORT=18962 OH_CANVAS_SAFE_VITE_PORT=18963 OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 "$@" > $Q/launcher.log 2>&1 & echo $! > $Q/pid; for i in $(seq 1 60); do c=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:18960/); [ "$c" = 200 ] && break; sleep 3; done; echo "code=$c"; }; start env -u LOCAL_BACKEND_API_KEY nohup node bin/agent-canvas.mjs --port 18960 (code=200). curl -s http://127.0.0.1:18960/ | grep -o '__AGENT_CANVAS_SESSION_API_KEY__[^;<]*' | md5sum prints a hash (never print the key itself); $Q/home/.openhands/agent-canvas/api-key.txt exists (64 hex characters) and curl -s -o /dev/null -w '%{http_code}' -H "X-Session-API-Key: $(cat $Q/home/.openhands/agent-canvas/api-key.txt)" http://127.0.0.1:18960/api/settings is 200. kill -TERM $(cat $Q/pid); sleep 6, run the same start ... line again in the same shell and repeat the md5sum line: the hash is identical.

Pinned session keyF26.session-key-pinnedMapped behavior

Scenario F26--pinned-session-key · Permalink · Source

Given

Use F26’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

  1. Do
    Open http://127.0.0.1:18960/.

Expect

LOCAL_BACKEND_API_KEY=<key> in local mode injects exactly that key into the page and the API accepts only it (the saved generated key gets 401).

Exact CLI recipe & bug checks

Same shell, after kill -TERM $(cat $Q/pid); sleep 6: start env LOCAL_BACKEND_API_KEY=qa-f26-pin-key nohup node bin/agent-canvas.mjs --port 18960. curl -s http://127.0.0.1:18960/ | grep -o '__AGENT_CANVAS_SESSION_API_KEY__[^;<]*' prints __AGENT_CANVAS_SESSION_API_KEY__="qa-f26-pin-key"; /api/settings is 200 with -H 'X-Session-API-Key: qa-f26-pin-key' and 401 with the saved generated key. In the browser, control-openhands browser goto http://127.0.0.1:18960/ --allow-external and control-openhands browser eval "({key: window.__AGENT_CANVAS_SESSION_API_KEY__ === 'qa-f26-pin-key', authRequired: window.__AGENT_CANVAS_AUTH_REQUIRED__ === true})" → {key: true, authRequired: false}.

Bind host from the environmentF26.host-bind-envMapped behavior

Scenario F26--bind-host-from-the-environment · Permalink · Source

Given

Use F26’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

  1. Do
    Same shell, after kill -TERM $(cat $Q/pid); sleep 6: start env LOCAL_BACKEND_API_KEY=qa-f26-pin-key OH_BIND_HOST=0.0.0.0 nohup node bin/agent-canvas.mjs --port 18960. grep -a 'not loopback' $Q/launcher.log shows the same [auth] and [static] warnings as --host 0.0.0.0, and curl -s http://127.0.0.1:18960/ | grep -o '__AGENT_CANVAS_\(SESSION_API_KEY\|AUTH_REQUIRED\)__' | sort -u prints only __AGENT_CANVAS_AUTH_REQUIRED__. Stop it with kill -TERM $(cat $Q/pid); after about 6 s ports 18960–18963 are free.

Expect

OH_BIND_HOST=0.0.0.0 (the environment form of --host) prints the same two "not loopback" warnings and does not inject the session key, even with LOCAL_BACKEND_API_KEY set.

Exact CLI recipe & bug checks

Same shell, after kill -TERM $(cat $Q/pid); sleep 6: start env LOCAL_BACKEND_API_KEY=qa-f26-pin-key OH_BIND_HOST=0.0.0.0 nohup node bin/agent-canvas.mjs --port 18960. grep -a 'not loopback' $Q/launcher.log shows the same [auth] and [static] warnings as --host 0.0.0.0, and curl -s http://127.0.0.1:18960/ | grep -o '__AGENT_CANVAS_\(SESSION_API_KEY\|AUTH_REQUIRED\)__' | sort -u prints only __AGENT_CANVAS_AUTH_REQUIRED__. Stop it with kill -TERM $(cat $Q/pid); after about 6 s ports 18960–18963 are free.

Frontend-only, returning userF26.frontend-only-returningKnown failure

Scenario F26--frontend-only-returning-user · Permalink · Source

Given

Use F26’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

  1. Do
    Start with a fresh browser profile.
  2. Do
    Open http://127.0.0.1:18960/.
  3. Wait
    Wait for “Telemetry consent form” to appear.
  4. Do
    Use browser uncheck as specified in the exact recipe.
  5. Do
    Choose “Confirm telemetry preferences”.
  6. Wait
    Wait for “Onboarding step choose agent” to appear.
  7. Do
    Choose “Onboarding skip”.
  8. Wait
    Wait for “Root layout” to appear.
  9. Do
    Open http://127.0.0.1:18960/.
  10. Wait
    Wait for “Agent server onboarding screen” to appear.
  11. Do
    Press Escape.
  12. Do
    Open /.
  13. Do
    Follow the onboarding setup in the recipe.

Expect

on a frontend-only origin, a browser that already stores a backend for that origin (the default-local entry a full launcher on the same port seeded, or one added there earlier) gets the recovery gate instead of onboarding: agent-server-onboarding-screen with Manage backends in recovery mode and the Local row Disconnected with the 503 detail (the gate itself is F25's recovery-gate row); the failing probes raise generic An error occurred toasts there (

Known bug observations

Known failure, reproduced 2026-10-08: #18160).

Linked known issues

Which screen a frontend-only origin shows depends on the browser, not the launcher: a profile new to the origin gets Add a backend (first run owns the initial backend), while a profile that stores a backend for that origin (the default-local entry a full launcher on the same port seeded, or any backend added there earlier) gets the recovery gate of F25.recovery-gate as soon as that backend answers 503 (F26.frontend-only-returning). The no-toast check belongs to the first-run state only: on the gate, browser toasts --history lists generic An error occurred status toasts raised by the failing 503 probes (two within 12 s here), with no hint of which request failed. Known failure (reproduced 2026-10-08): both toasts come from the free-models hydrator's GET /api/llm/models/verified 503, which no meta.disableToast guards on that screen (nor does backend-version.tsx); #18160.

Exact CLI recipe & bug checks

Same shell, after kill -TERM $(cat $Q/pid); sleep 6. Arrange a browser that onboarded on this origin against the full launcher: start env LOCAL_BACKEND_API_KEY=qa-f26-pin-key nohup node bin/agent-canvas.mjs --port 18960 (code=200), control-openhands browser reset (a fresh profile, which also forgets your run's onboarding: restore it at the end), control-openhands browser goto http://127.0.0.1:18960/ --allow-external, control-openhands browser wait 'testid=telemetry-consent-form' --timeout 15000 (this backend state has never answered consent; a second pass on the same $Q skips the form), control-openhands browser uncheck 'testid=telemetry-consent-form >> role=checkbox', control-openhands browser click 'testid=confirm-telemetry-preferences', control-openhands browser wait 'testid=onboarding-step-choose-agent' --timeout 15000, control-openhands browser click 'testid=onboarding-skip' and control-openhands browser wait 'testid=root-layout' --timeout 20000. control-openhands browser eval "JSON.parse(localStorage.getItem('openhands-backends')).map(b=>({id:b.id,name:b.name,host:b.host}))" is [{id: "default-local", name: "Local", host: "http://127.0.0.1:18960"}]. Now serve the same port without backends: kill -TERM $(cat $Q/pid); sleep 6; start nohup node bin/agent-canvas.mjs --frontend-only --port 18960 (code=200, and curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:18960/server_info is 503). control-openhands browser goto http://127.0.0.1:18960/ --allow-external and control-openhands browser wait 'testid=agent-server-onboarding-screen' --timeout 20000 succeed: the gate of F25.recovery-gate, not onboarding. control-openhands browser count is 1 for testid=manage-backends-modal and 0 for testid=onboarding-step-check-backend, testid=first-run-onboarding-screen, testid=api-key-entry-screen, testid=root-layout, testid=close-manage-backends-modal and testid=manage-backends-done; control-openhands browser press Escape leaves the modal (count 1). control-openhands browser text 'testid=manage-backends-row-Local' reads Local, http://127.0.0.1:18960, Disconnected, HTTP request failed (503 Service Unavailable): "Service Unavailable (no backend configured for this route)" and LOCAL, and control-openhands browser testids lists manage-backends-add (Add Backend) as the way on. control-openhands browser screenshot --feature F26.frontend-only-returning --name recovery-gate. The 503 probes are expected errors in the sweep: control-openhands browser errors (without --app-only, which keeps only your run's origin) lists external:http-error rows with 503 for http://127.0.0.1:18960/server_info, /api/settings and /api/llm/models/verified, and pageErrors is 0. Stop it with kill -TERM $(cat $Q/pid); after about 6 s ports 18960–18963 are free. Restore your run: control-openhands browser goto / and control-openhands onboard --skip.

Automation exits during startupF26.automation-startup-failureKnown failure

Scenario F26--automation-exits-during-startup · Permalink · Source

Given

Use F26’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

  1. Do
    With 18960–18963 free, start a loopback launcher whose automation cannot install: Q=$OH_VERIFY_RUN/private/f26-autofail; mkdir -p $Q; HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state OH_CANVAS_SAFE_BACKEND_PORT=18961 OH_CANVAS_SAFE_AUTOMATION_PORT=18962 OH_CANVAS_SAFE_VITE_PORT=18963 OH_AUTOMATION_VERSION=0.0.1 LOCAL_BACKEND_API_KEY=qa-f26-autofail-key OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 nohup node bin/agent-canvas.mjs --port 18960 > $Q/launcher.log 2>&1 & echo $! > $Q/pid, then poll the log until Main UI appears or the pid exits. Expected (#18362): no Main UI banner, a message naming the automation startup failure, a nonzero exit and closed ports. Known failure (reproduced 2026-10-13 twice, with 0.0.1 and 0.0.0-qa-missing): the log shows [automation] ... No solution found ... and [automation] Exited with code 1 (code 2 for the unparsable version), then Main UI: http://localhost:18960/ two seconds later; kill -0 $(cat $Q/pid) succeeds, curl http://127.0.0.1:18960/ is 200 and /api/automation/health (with the key) is 502. Stop it with kill -TERM $(cat $Q/pid); the ports free within seconds. The #18299 migration failure in the issue is the same path through a different exit cause.

Expect

when the requested automation backend exits before it is ready, the launcher should print no success banner, stop the services it started and exit nonzero with the automation's exit reason.

Known bug observations

Known failure (reproduced 2026-10-13 twice, with 0.0.1 and 0.0.0-qa-missing): the log shows [automation] ... No solution found ... and [automation] Exited with code 1 (code 2 for the unparsable version), then Main UI: http://localhost:18960/ two seconds later; kill -0 $(cat $Q/pid) succeeds, curl http://127.0.0.1:18960/ is 200 and /api/automation/health (with the key) is 502. Stop it with kill -TERM $(cat $Q/pid); the ports free within seconds. The #18299 migration failure in the issue is the same path through a different exit cause.

Known failure (reproduced 2026-10-13, #18362): it prints the Main UI banner and keeps the stack up with /api/automation/health answering 502.

Exact CLI recipe & bug checks

With 18960–18963 free, start a loopback launcher whose automation cannot install: Q=$OH_VERIFY_RUN/private/f26-autofail; mkdir -p $Q; HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state OH_CANVAS_SAFE_BACKEND_PORT=18961 OH_CANVAS_SAFE_AUTOMATION_PORT=18962 OH_CANVAS_SAFE_VITE_PORT=18963 OH_AUTOMATION_VERSION=0.0.1 LOCAL_BACKEND_API_KEY=qa-f26-autofail-key OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 nohup node bin/agent-canvas.mjs --port 18960 > $Q/launcher.log 2>&1 & echo $! > $Q/pid, then poll the log until Main UI appears or the pid exits. Expected (#18362): no Main UI banner, a message naming the automation startup failure, a nonzero exit and closed ports. Known failure (reproduced 2026-10-13 twice, with 0.0.1 and 0.0.0-qa-missing): the log shows [automation] ... No solution found ... and [automation] Exited with code 1 (code 2 for the unparsable version), then Main UI: http://localhost:18960/ two seconds later; kill -0 $(cat $Q/pid) succeeds, curl http://127.0.0.1:18960/ is 200 and /api/automation/health (with the key) is 502. Stop it with kill -TERM $(cat $Q/pid); the ports free within seconds. The #18299 migration failure in the issue is the same path through a different exit cause.

Runtime servicesF26.runtime-servicesKnown issue

Scenario F26--runtime-services · Permalink · Source

Given

Use F26’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

  1. Setup
    Follow the environment or fixture command in the exact recipe.
  2. Do
    Start a conversation with the prompt “Reply with the single word: ok”.

Expect

/server_info.runtime_services lists the agent-server, ingress, frontend and automation URLs as the agent sees them, and a new conversation's system prompt carries a matching <RUNTIME_SERVICES> block.

Linked known issues

Known issue #16720 (F26.runtime-services): CPU-heavy commands in several conversations can starve Agent Server and stop the UI loading; the report requests lower priority for agent commands than the service. Recheck with a disposable bounded workload, recorded CPU/resource limits and real command observations, never arbitrary stress on the shared machine. The exact Rust workload and affected resource envelope are missing, and real agent execution needs a model key. The backend counterpart is OpenHands/software-agent-sdk#4575.

Known issue #16690 (F26.docker-image F26.runtime-services): A Docker deployment with nine chats becomes slow/disconnected, then reloads lose the list and startup proxies report ECONNREFUSED. Recheck on the affected Windows-host Docker configuration with retained conversation state and a controlled workload; creating a few empty local chats cannot prove the reported resource/runtime failure. This run lacks Docker and the original conversation/workload fixture.

Known issue #16300 (F26.runtime-services): The npm launcher can fail while building LiteLLM native dependencies when resolved AWS crates require a newer Rust compiler, leaving backend services unavailable. Recheck an isolated uncached uv environment with litellm==1.95.0, the reported Rust 1.93.1/toolchain/platform and dependency resolution; a warm current backend cache cannot exercise that install failure. The historical dependency/toolchain fixture is unavailable.

Known issue #16208 (F26.runtime-services): On Fedora Rawhide/Python 3.14.6, settings work but starting any conversation returns HTTP 500. Recheck a native Fedora environment with the reported SDK/runtime and model/profile, including the underlying server traceback; this macOS current-runtime stack cannot prove that platform-specific failure.

Known issue OpenHands/software-agent-sdk#4901 (F26.runtime-services): closing an ACP conversation on POSIX is reported to leave the adapter's descendant Node/CLI processes alive after the top npm process exits. Recheck an owned authenticated ACP provider command chain, record its exact descendant identities before close and inspect only those identities afterwards; cleanup must target those owned groups. The current run has no authorized authenticated ACP runtime for this path.

Known issue OpenHands/software-agent-sdk#5974 (F26.runtime-services; workspace picker in F03): a saved-workspace mutation waiting for its registry file lock can block the Agent Server event loop, including /alive, until the lock is released. Recheck only an owned registry using a bounded lock holder, an independently confirmed waiting mutation, concurrent liveness probes and a successful post-release readback; the original evidence uses Linux kernel lock-wait readback. A healthy idle server or concurrent writes without deliberate lock contention do not cover it.

Exact CLI recipe & bug checks

Run control-openhands api GET /server_info --pick runtime_services.mode (agent-canvas) and control-openhands api GET /server_info --pick runtime_services.services.automation.url_from_agent (http://localhost:<automation port>; control-openhands status prints the ports). Then control-openhands conversation start --prompt "Reply with the single word: ok" --wait --timeout 180 (prints <id>) and control-openhands conversation events <id> --grep RUNTIME_SERVICES --from-start: the SystemPromptEvent matches, with an excerpt <RUNTIME_SERVICES>\nYou are running inside an agent-canvas dev stack started .... control-openhands conversation events <id> --grep "localhost:<automation port>" --from-start shows * Automation backend: http://localhost:<port> plus its Docs: and OpenAPI: URLs.

The agent uses the runtime servicesF26.runtime-services-agent-useMapped behavior

Scenario F26--the-agent-uses-the-runtime-services · Permalink · Source

Given

Use F26’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

  1. Do
    Start a conversation with the prompt “$(cat qa-f26-agent.txt)”.
  2. Do
    Open /automations.
  3. Do
    Choose the element identified in the CLI recipe.
  4. Setup
    Arrange the prerequisite backend state through the API.

Expect

with that block alone, the agent can reach the automation backend from its terminal: asked to, it reads the OpenAPI at the URL the block names, creates an enabled cron automation, and dispatches a run that completes; the automation and its run show up in the Automations UI.

Exact CLI recipe & bug checks

Write qa-f26-agent.txt (outside the workspace) containing: Using the Automation backend described in your RUNTIME_SERVICES (read its OpenAPI to find the routes), create one automation named QA_rt_auto from a prompt: the prompt is "Reply with the single word: pong. Do not run any tools.", the schedule is the cron 0 9 * * *, and it is enabled. Then dispatch one run of it now. Do not change anything else. Reply with the automation id and the run id only. Run control-openhands conversation start --prompt "$(cat qa-f26-agent.txt)" --wait --timeout 400 (note <agent-id>); the reply names <automation-id> and a run id. control-openhands conversation events <agent-id> --kinds ActionEvent --from-start shows the agent's own curl calls: http://localhost:<automation port>/api/automation/openapi.json, POST …/api/automation/v1/preset/prompt and POST …/api/automation/v1/<automation-id>/dispatch, each with the X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY header the block names. Second view in the UI: control-openhands browser goto /automations and control-openhands browser text '[data-testid^=automation-card-] >> has-text=QA_rt_auto' (QA_rt_auto, the prompt, cron, Runs 1). Open it with control-openhands browser click '[data-testid^=automation-card-] >> has-text=QA_rt_auto >> text=QA_rt_auto' --expect-url '/automations/[0-9a-f-]+'. Within about a minute, control-openhands browser text 'testid=automation-activity-log' shows the run's summary (Replied with the single word "pong" …), a cost and Successful (control-openhands browser screenshot --feature F26.runtime-services-agent-use --name agent-created-automation-run). control-openhands api GET /api/automation/v1/<automation-id> has name QA_rt_auto, trigger.schedule 0 9 * * * and enabled true. Delete it afterwards (control-openhands api DELETE /api/automation/v1/<automation-id> --write, arrange). It runs daily at 09:00 UTC otherwise.

Library buildF26.lib-buildMapped behavior

Scenario F26--library-build · Permalink · Source

Given

Use F26’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

  1. Do
    Never build inside the shared checkout (it regenerates i18n and typegen files the other runs hash); build in a copy: L=$OH_VERIFY_RUN/private/f26-lib; mkdir -p $L; tar --exclude=./node_modules --exclude=./build --exclude=./.git --exclude=./dist -cf - . | tar -xf - -C $L; ln -s "$PWD/node_modules" $L/node_modules; (cd $L && npm run build:lib); echo "exit=$?". It ends with ✓ built in and exit=0 (about 30 s). Then (cd $L && node -e 'const p=require("./package.json"),fs=require("fs");let ok=0,miss=[];for(const[k,v]of Object.entries(p.exports))for(const f of(typeof v==="string"?[v]:Object.values(v)))fs.existsSync(f)?ok++:miss.push(k+" -> "+f);console.log(JSON.stringify({entries:Object.keys(p.exports),ok,missing:miss}))') prints 9 entries (., ./browser, ./conversation, ./files, ./settings, ./sidebar, ./terminal, ./i18n, ./package.json), ok 25 and missing []. grep -ohE 'AgentServerUIProviders|AgentServerUIRoot|CloudOrganizationBoundary|configureTelemetry|setTelemetryConsent' $L/dist/lib/index.d.ts | sort -u lists all five. Remove the copy with rm -rf $L.

Expect

npm run build:lib produces dist/ where every package.json exports target exists and the main entry exports AgentServerUIProviders, AgentServerUIRoot, CloudOrganizationBoundary and the telemetry helpers.

Exact CLI recipe & bug checks

Never build inside the shared checkout (it regenerates i18n and typegen files the other runs hash); build in a copy: L=$OH_VERIFY_RUN/private/f26-lib; mkdir -p $L; tar --exclude=./node_modules --exclude=./build --exclude=./.git --exclude=./dist -cf - . | tar -xf - -C $L; ln -s "$PWD/node_modules" $L/node_modules; (cd $L && npm run build:lib); echo "exit=$?". It ends with ✓ built in and exit=0 (about 30 s). Then (cd $L && node -e 'const p=require("./package.json"),fs=require("fs");let ok=0,miss=[];for(const[k,v]of Object.entries(p.exports))for(const f of(typeof v==="string"?[v]:Object.values(v)))fs.existsSync(f)?ok++:miss.push(k+" -> "+f);console.log(JSON.stringify({entries:Object.keys(p.exports),ok,missing:miss}))') prints 9 entries (., ./browser, ./conversation, ./files, ./settings, ./sidebar, ./terminal, ./i18n, ./package.json), ok 25 and missing []. grep -ohE 'AgentServerUIProviders|AgentServerUIRoot|CloudOrganizationBoundary|configureTelemetry|setTelemetryConsent' $L/dist/lib/index.d.ts | sort -u lists all five. Remove the copy with rm -rf $L.

Style scopeF26.lib-style-scopeMapped behavior

Scenario F26--style-scope · Permalink · Source

Given

Use F26’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

  1. Do
    Open /.

Expect

all bundled CSS is scoped under [data-agent-server-ui], and theme tokens such as --oh-color-base live on that scope root (observable in the standalone app).

Exact CLI recipe & bug checks

On your run's home page (control-openhands browser goto /) run control-openhands browser eval "(() => { let total=0, unscoped=[]; for (const sh of document.styleSheets) { let rules; try { rules = sh.cssRules } catch { continue } const walk = (rs) => { for (const r of rs) { if (r.selectorText) { total++; if (!r.selectorText.includes('data-agent-server-ui')) unscoped.push(r.selectorText) } else if (r.cssRules) walk(r.cssRules) } }; walk(rules) } return { scopeRoots: document.querySelectorAll('[data-agent-server-ui]').length, total, unscopedCount: unscoped.length, sample: unscoped.slice(0, 12) } })()". total is in the thousands and unscopedCount is 2: only .go<hash> and .go<hash> > *, react-hot-toast's runtime classes. control-openhands browser eval "getComputedStyle(document.querySelector('[data-agent-server-ui]')).getPropertyValue('--oh-color-base').trim()" is #181818. This proves the bundle is scoped, not that a host page is unaffected (F26.lib-host-app).

BlockedF26.lib-host-app · F26.docker-image · F26.docker-conversation-runtime · F26.helm-chartKnown issueBlocked

Scenario F26--blocked · Permalink · Source

Given

Use F26’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

  1. Do
    If docker info cannot reach the daemon but dockerd is installed, start it (setsid dockerd); without one, record both Docker rows blocked naming the missing daemon. With a daemon, run the image with -p 127.0.0.1:8000:8000, open http://localhost:8000/canvas and expect the Add a backend step (testid=onboarding-step-check-backend, Next disabled until a key is typed); onboarding-skip leads to testid=api-key-entry-screen. With -e AGENT_CANVAS_ALLOW_LAN_SESSION_KEY=true the key is injected and first run opens on Choose your agent, under the telemetry consent dialog. npm run build:docker behind a TLS-intercepting proxy needs the proxy CA inside the build stages (without it uv pip install / pip install in the final stage fail with CERTIFICATE_VERIFY_FAILED: self-signed certificate in certificate chain), and Docker Hub may answer 429. Build a scratch copy of the Dockerfile that only adds the CA, never an edited docker/Dockerfile: after FROM node:24-slim AS frontend-build add COPY --from=ccrca ca-bundle.crt /tmp/qa-ca.crt and ENV NODE_EXTRA_CA_CERTS=/tmp/qa-ca.crt npm_config_cafile=/tmp/qa-ca.crt, after the first USER root add the same COPY and ENV SSL_CERT_FILE=/tmp/qa-ca.crt PIP_CERT=/tmp/qa-ca.crt REQUESTS_CA_BUNDLE=/tmp/qa-ca.crt UV_NATIVE_TLS=1, then docker build -f <copy> --build-context ccrca=<dir holding ca-bundle.crt> --network host --build-arg AGENT_SERVER_IMAGE=<images.agentServer>:<versions.agentServer>-python --build-arg AGENT_SERVER_VERSION=<versions.agentServer> --build-arg AUTOMATION_VERSION=<versions.automation> --build-arg VITE_BASE_PATH=/canvas -t qa-agent-canvas:target . (the pins scripts/docker-build.mjs reads from config/defaults.json; about 5 min on a warm cache). Publish it on a free port (-p 127.0.0.1:<free>:8000; 8000 and the 188xx blocks may belong to other runs). Docker Hub may answer 429 Too Many Requests for node:24-slim; in the scratch copy only, replace both FROM node:24-slim lines with FROM mirror.gcr.io/library/node:24-slim (arrange; say so in the note). Run the unmodified image with docker run -d --name <qa-name> -p 127.0.0.1:<free>:8000 qa-agent-canvas:target. On a kernel without IPv6, docker logs <qa-name> shows IPv6 is not available; binding the frontend to 0.0.0.0. and Static-server + proxy listening on http://0.0.0.0:8000/canvas/, /canvas/ answers 200 within about 25 s and docker inspect -f '{{.State.Status}}' <qa-name> is still running minutes later (#18178 fixed by #18180, verified 2026-10-13); docker exec <qa-name> ls /opt/agent-canvas lists the mars-*.mjs bridge modules (#18343). The container's generated key is docker exec <qa-name> cat /home/openhands/.openhands/agent-canvas/api-key.txt (write it to $OH_VERIFY_RUN/private/, fill with --value-file). Settings → Application (/canvas/settings/app) shows the image's Version (agent-canvas-update-card), which matches jq -r .version package.json. Remove the containers and the image afterwards (docker rm -f, docker rmi). Record F26.lib-host-app blocked naming the missing host-app example. command -v helm kubectl prints nothing here: record F26.helm-chart blocked naming the missing helm binary and cluster; with them, helm lint helm/agent-canvas and helm template qa helm/agent-canvas render the StatefulSet, Service and Ingress, and helm install follows helm/agent-canvas/README.md.

Expect

mounted in a separate host app, Canvas styles stay inside the scope and styleOverrides restyle it. Blocked: no host-app example exists.

docker run ghcr.io/openhands/agent-canvas serves Canvas at /canvas; without AGENT_CANVAS_ALLOW_LAN_SESSION_KEY=true the UI asks for the key. Blocked without a Docker daemon. On a host whose kernel has no IPv6 (no /proc/net/if_inet6) the entrypoint logs IPv6 is not available; binding the frontend to 0.0.0.0. and the container keeps running (#18180).

OH_CONVERSATION_RUNTIME=docker runs each new conversation in its own container. Blocked without a Docker daemon.

helm install agent-canvas ./helm/agent-canvas runs the Docker image as a Kubernetes StatefulSet with a PVC and an Ingress. Blocked without helm and a cluster.

Linked known issues

Known issue #17763 (F26.docker-image): The Docker entrypoint can leave protected Agent Server APIs unauthenticated when only LOCAL_BACKEND_API_KEY is supplied, because its export to OH_SESSION_API_KEYS_0 is inside the generate-key branch. Recheck an owned container with a dummy custom key: unauthenticated GET /api/conversations/search must be 401 and the same request with that dummy key 200. /server_info is intentionally public and is not an auth probe. Blocked here without a Docker daemon.

Known issue #17623 (F26.helm-chart): The chart defaults service-account-token automounting to true even with RBAC disabled, exposing an unused cluster credential to agent processes. Recheck a disposable Kubernetes installation using the default values and an explicit opt-in; inspect token existence/readability only, never its contents. Blocked without Helm and a Kubernetes cluster.

Known issue #17470 (F26.docker-image): On Apple Silicon under Colima/Virtualization.framework, the image's Python services can exit with SIGILL while the frontend leaves onboarding at Disconnected. The report isolates import litellm and compares OPENSSL_armcap=0; recheck on the affected ARM/Colima guest with the reported cryptography/OpenSSL combination. A native macOS npm stack is not that container environment.

Known issue #16690 (F26.docker-image F26.runtime-services): A Docker deployment with nine chats becomes slow/disconnected, then reloads lose the list and startup proxies report ECONNREFUSED. Recheck on the affected Windows-host Docker configuration with retained conversation state and a controlled workload; creating a few empty local chats cannot prove the reported resource/runtime failure. This run lacks Docker and the original conversation/workload fixture.

Known issue #16511 (F26.docker-image): The documented Docker bind mount can be unwritable to container uid 10001, killing Agent Server/automation while the frontend still serves Bad Gateway. Recheck a disposable host-owned mode-700 mount and a writable control against an owned image; never chmod the operator's real state directory. Blocked here without Docker.

Known issue OpenHands/software-agent-sdk#5310 (F26.docker-conversation-runtime): A Docker conversation can lose the auxiliary title profile at the runtime boundary, leaving an ACP conversation untitled or producing an unrelated title-auth error while the agent still works. Recheck a saved reachable title profile and an ACP conversation inside an owned per-conversation Docker runtime, comparing its title/profile metadata with the host; inspect only profile names and credential-presence booleans. Blocked without Docker and real auxiliary-title/ACP credentials.

Known issue OpenHands/software-agent-sdk#5307 (F26.docker-conversation-runtime): New worktree on a Docker-backed conversation can create git metadata whose reverse path exists only inside the container, so the still-live host worktree reads prunable gitdir file points to non-existent location. Recheck a disposable real git repository mounted into an owned conversation container, comparing host and container git worktree list/status while it is alive; a local worktree does not test the filesystem boundary. Blocked without Docker.

Exact CLI recipe & bug checks

If docker info cannot reach the daemon but dockerd is installed, start it (setsid dockerd); without one, record both Docker rows blocked naming the missing daemon. With a daemon, run the image with -p 127.0.0.1:8000:8000, open http://localhost:8000/canvas and expect the Add a backend step (testid=onboarding-step-check-backend, Next disabled until a key is typed); onboarding-skip leads to testid=api-key-entry-screen. With -e AGENT_CANVAS_ALLOW_LAN_SESSION_KEY=true the key is injected and first run opens on Choose your agent, under the telemetry consent dialog. npm run build:docker behind a TLS-intercepting proxy needs the proxy CA inside the build stages (without it uv pip install / pip install in the final stage fail with CERTIFICATE_VERIFY_FAILED: self-signed certificate in certificate chain), and Docker Hub may answer 429. Build a scratch copy of the Dockerfile that only adds the CA, never an edited docker/Dockerfile: after FROM node:24-slim AS frontend-build add COPY --from=ccrca ca-bundle.crt /tmp/qa-ca.crt and ENV NODE_EXTRA_CA_CERTS=/tmp/qa-ca.crt npm_config_cafile=/tmp/qa-ca.crt, after the first USER root add the same COPY and ENV SSL_CERT_FILE=/tmp/qa-ca.crt PIP_CERT=/tmp/qa-ca.crt REQUESTS_CA_BUNDLE=/tmp/qa-ca.crt UV_NATIVE_TLS=1, then docker build -f <copy> --build-context ccrca=<dir holding ca-bundle.crt> --network host --build-arg AGENT_SERVER_IMAGE=<images.agentServer>:<versions.agentServer>-python --build-arg AGENT_SERVER_VERSION=<versions.agentServer> --build-arg AUTOMATION_VERSION=<versions.automation> --build-arg VITE_BASE_PATH=/canvas -t qa-agent-canvas:target . (the pins scripts/docker-build.mjs reads from config/defaults.json; about 5 min on a warm cache). Publish it on a free port (-p 127.0.0.1:<free>:8000; 8000 and the 188xx blocks may belong to other runs). Docker Hub may answer 429 Too Many Requests for node:24-slim; in the scratch copy only, replace both FROM node:24-slim lines with FROM mirror.gcr.io/library/node:24-slim (arrange; say so in the note). Run the unmodified image with docker run -d --name <qa-name> -p 127.0.0.1:<free>:8000 qa-agent-canvas:target. On a kernel without IPv6, docker logs <qa-name> shows IPv6 is not available; binding the frontend to 0.0.0.0. and Static-server + proxy listening on http://0.0.0.0:8000/canvas/, /canvas/ answers 200 within about 25 s and docker inspect -f '{{.State.Status}}' <qa-name> is still running minutes later (#18178 fixed by #18180, verified 2026-10-13); docker exec <qa-name> ls /opt/agent-canvas lists the mars-*.mjs bridge modules (#18343). The container's generated key is docker exec <qa-name> cat /home/openhands/.openhands/agent-canvas/api-key.txt (write it to $OH_VERIFY_RUN/private/, fill with --value-file). Settings → Application (/canvas/settings/app) shows the image's Version (agent-canvas-update-card), which matches jq -r .version package.json. Remove the containers and the image afterwards (docker rm -f, docker rmi). Record F26.lib-host-app blocked naming the missing host-app example. command -v helm kubectl prints nothing here: record F26.helm-chart blocked naming the missing helm binary and cluster; with them, helm lint helm/agent-canvas and helm template qa helm/agent-canvas render the StatefulSet, Service and Ingress, and helm install follows helm/agent-canvas/README.md.

Desktop splash and windowF26.desktop-boot-splash · F26.desktop-main-windowKnown issue

Scenario F26--desktop-splash-and-window · Permalink · Source

Given

Use F26’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

  1. Do
    Start a private display and the app in one process group: Q=$OH_VERIFY_RUN/private/f26-desktop; mkdir -p $Q/shots; setsid nohup sh -c "echo \$\$ > $Q/pgid; Xvfb :78 -screen 0 1440x1000x24 -nolisten tcp & sleep 1; DISPLAY=:78 HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state LOCAL_BACKEND_API_KEY=qa-f26-desktop-key OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 exec node_modules/.bin/electron --no-sandbox electron" > $Q/desktop.log 2>&1 &, then capture frames: for i in $(seq 1 40); do sleep 1; DISPLAY=:78 import -window root $Q/shots/s$(printf %02d $i).png; done. Read the frames: the first seconds show the splash (logo, OpenHands Agent Canvas, AI coding agent interface, spinner, Starting backend services…, then lines such as agent-server: {"asctime": ..., the note First launch downloads Python + the OpenHands agent server. and a Show details button); a later frame shows the main window with a File Edit View Window menu and the first-run Choose your agent step under the consent dialog naming the local backend "Local" at http://localhost:8000 (about 30 s on a warm cache, minutes on the first download). While it runs, curl -s http://127.0.0.1:8000/mars/health | head -c 15 prints <!DOCTYPE html>: the desktop stack's ingress hosts no bridge (marsWebBridge: false, F26.mars-web-bridge). Copy the frames you cite into $OH_VERIFY_RUN/evidence/F26.desktop-boot-splash/ and .../F26.desktop-main-window/. Stop in two steps: kill -TERM $(cat $Q/pgid) signals only the Electron launcher (it quits and stops its backend services); after about 10 s ports 8000, 18000, 18001 and 3001 are free again. Then kill -TERM -- -$(cat $Q/pgid) ends Xvfb; ps -o pid,cmd -g $(cat $Q/pgid) lists nothing.

Expect

the Electron app first shows a dark splash (logo, OpenHands Agent Canvas, spinner, Starting backend services…, then live service-log lines, a first-launch note and Show details).

after boot a native window with a File/Edit/View/Window menu loads http://localhost:8000 with the key injected (first run, no API-key screen).

Linked known issues

Known issue #17112 (F26.desktop-main-window F26.desktop-external-links): Desktop main-window navigation, an OAuth popup redirect, or a nested popup can render a remote page inside the app frame. Recheck all three on an owned running Electron shell with a harmless remote page and loopback redirect fixture; the browser harness cannot operate Electron windows and the installed Electron binary is unavailable here.

Known issue #16774 (F26.desktop-boot-splash F26.desktop-main-window): The macOS 1.14.0 DMG installs an app that Gatekeeper reports as damaged instead of booting. Recheck the actual downloaded signed/notarized release artifact through install and first launch on macOS; starting source Electron or removing quarantine would bypass the reported failure. That release DMG/signature fixture is unavailable in this run.

Exact CLI recipe & bug checks

Start a private display and the app in one process group: Q=$OH_VERIFY_RUN/private/f26-desktop; mkdir -p $Q/shots; setsid nohup sh -c "echo \$\$ > $Q/pgid; Xvfb :78 -screen 0 1440x1000x24 -nolisten tcp & sleep 1; DISPLAY=:78 HOME=$Q/home OH_CANVAS_SAFE_STATE_DIR=$Q/state LOCAL_BACKEND_API_KEY=qa-f26-desktop-key OH_SECRET_KEY=qa-f26-secret DO_NOT_TRACK=1 exec node_modules/.bin/electron --no-sandbox electron" > $Q/desktop.log 2>&1 &, then capture frames: for i in $(seq 1 40); do sleep 1; DISPLAY=:78 import -window root $Q/shots/s$(printf %02d $i).png; done. Read the frames: the first seconds show the splash (logo, OpenHands Agent Canvas, AI coding agent interface, spinner, Starting backend services…, then lines such as agent-server: {"asctime": ..., the note First launch downloads Python + the OpenHands agent server. and a Show details button); a later frame shows the main window with a File Edit View Window menu and the first-run Choose your agent step under the consent dialog naming the local backend "Local" at http://localhost:8000 (about 30 s on a warm cache, minutes on the first download). While it runs, curl -s http://127.0.0.1:8000/mars/health | head -c 15 prints <!DOCTYPE html>: the desktop stack's ingress hosts no bridge (marsWebBridge: false, F26.mars-web-bridge). Copy the frames you cite into $OH_VERIFY_RUN/evidence/F26.desktop-boot-splash/ and .../F26.desktop-main-window/. Stop in two steps: kill -TERM $(cat $Q/pgid) signals only the Electron launcher (it quits and stops its backend services); after about 10 s ports 8000, 18000, 18001 and 3001 are free again. Then kill -TERM -- -$(cat $Q/pgid) ends Xvfb; ps -o pid,cmd -g $(cat $Q/pgid) lists nothing.

KV survives authentication rotationF26.session-key-rotated · F26.session-key-persistKnown failure

Scenario F26--kv-survives-authentication-rotation · Permalink · Source

Given

Use F26’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

  1. Do
    With no explicit AUTOMATION_KV_SECRET, create an owned inert automation and PUT a harmless marker to /api/automation/v1/kv/qa-session-rotation?automation_id=<id> through api --write; GET must return that marker. Run restart --rotate-key and doctor, then GET again using the helper's fresh private key. Expected: rotating authentication preserves encrypted automation state. Known failure, reproduced 2026-10-12 against automation 1.19.3: the pre-rotation read is 200 and the fresh-auth read is 500 Failed to decrypt value (#18301). This uses only disposable state and does not establish the original incident's data-loss history.

Expect

in local mode a stale session key left in the browser (the launcher's key changed since the last visit) is replaced by the injected key on load: the app opens on Home without the API-key screen, and the stored Local backend holds the new key (public mode asks for it instead: F01's api-key-entry row).

in local mode without LOCAL_BACKEND_API_KEY the launcher generates a session key, saves it under $HOME/.openhands/agent-canvas/api-key.txt and injects the same key again on the next start.

Known bug observations

Known failure, reproduced 2026-10-12 against automation 1.19.3: the pre-rotation read is 200 and the fresh-auth read is 500 Failed to decrypt value (#18301). This uses only disposable state and does not establish the original incident's data-loss history.

Exact CLI recipe & bug checks

With no explicit AUTOMATION_KV_SECRET, create an owned inert automation and PUT a harmless marker to /api/automation/v1/kv/qa-session-rotation?automation_id=<id> through api --write; GET must return that marker. Run restart --rotate-key and doctor, then GET again using the helper's fresh private key. Expected: rotating authentication preserves encrypted automation state. Known failure, reproduced 2026-10-12 against automation 1.19.3: the pre-rotation read is 200 and the fresh-auth read is 500 Failed to decrypt value (#18301). This uses only disposable state and does not establish the original incident's data-loss history.

Partial SQLite migration startupF26.runtime-servicesKnown failureKnown issue

Scenario F26--partial-sqlite-migration-startup · Permalink · Source

Given

Use F26’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

  1. Do
    Stop only the owned automation service, make a cold backup of <run>/private/automation/automations.db, and confirm its automation_git_sync_org_config already has consecutive_auth_failures. In that disposable DB only, set alembic_version.version_num to 028; restart with a bounded timeout, then open /automations. Expected: required-service startup fails explicitly or the partial schema recovers without corrupting state. Known failure, reproduced 2026-10-12 against automation 1.19.3: migration 029 reports the duplicate column and exits 3, the launcher remains alive, and Canvas shows Automations Unavailable (#18299). Restore the cold backup, restart and doctor before continuing. Never apply this fixture to an operator database.

Expect

/server_info.runtime_services lists the agent-server, ingress, frontend and automation URLs as the agent sees them, and a new conversation's system prompt carries a matching <RUNTIME_SERVICES> block.

Known bug observations

Known failure, reproduced 2026-10-12 against automation 1.19.3: migration 029 reports the duplicate column and exits 3, the launcher remains alive, and Canvas shows Automations Unavailable (#18299). Restore the cold backup, restart and doctor before continuing. Never apply this fixture to an operator database.

Linked known issues

Known issue #16720 (F26.runtime-services): CPU-heavy commands in several conversations can starve Agent Server and stop the UI loading; the report requests lower priority for agent commands than the service. Recheck with a disposable bounded workload, recorded CPU/resource limits and real command observations, never arbitrary stress on the shared machine. The exact Rust workload and affected resource envelope are missing, and real agent execution needs a model key. The backend counterpart is OpenHands/software-agent-sdk#4575.

Known issue #16690 (F26.docker-image F26.runtime-services): A Docker deployment with nine chats becomes slow/disconnected, then reloads lose the list and startup proxies report ECONNREFUSED. Recheck on the affected Windows-host Docker configuration with retained conversation state and a controlled workload; creating a few empty local chats cannot prove the reported resource/runtime failure. This run lacks Docker and the original conversation/workload fixture.

Known issue #16300 (F26.runtime-services): The npm launcher can fail while building LiteLLM native dependencies when resolved AWS crates require a newer Rust compiler, leaving backend services unavailable. Recheck an isolated uncached uv environment with litellm==1.95.0, the reported Rust 1.93.1/toolchain/platform and dependency resolution; a warm current backend cache cannot exercise that install failure. The historical dependency/toolchain fixture is unavailable.

Known issue #16208 (F26.runtime-services): On Fedora Rawhide/Python 3.14.6, settings work but starting any conversation returns HTTP 500. Recheck a native Fedora environment with the reported SDK/runtime and model/profile, including the underlying server traceback; this macOS current-runtime stack cannot prove that platform-specific failure.

Known issue OpenHands/software-agent-sdk#4901 (F26.runtime-services): closing an ACP conversation on POSIX is reported to leave the adapter's descendant Node/CLI processes alive after the top npm process exits. Recheck an owned authenticated ACP provider command chain, record its exact descendant identities before close and inspect only those identities afterwards; cleanup must target those owned groups. The current run has no authorized authenticated ACP runtime for this path.

Known issue OpenHands/software-agent-sdk#5974 (F26.runtime-services; workspace picker in F03): a saved-workspace mutation waiting for its registry file lock can block the Agent Server event loop, including /alive, until the lock is released. Recheck only an owned registry using a bounded lock holder, an independently confirmed waiting mutation, concurrent liveness probes and a successful post-release readback; the original evidence uses Linux kernel lock-wait readback. A healthy idle server or concurrent writes without deliberate lock contention do not cover it.

Exact CLI recipe & bug checks

Stop only the owned automation service, make a cold backup of <run>/private/automation/automations.db, and confirm its automation_git_sync_org_config already has consecutive_auth_failures. In that disposable DB only, set alembic_version.version_num to 028; restart with a bounded timeout, then open /automations. Expected: required-service startup fails explicitly or the partial schema recovers without corrupting state. Known failure, reproduced 2026-10-12 against automation 1.19.3: migration 029 reports the duplicate column and exits 3, the launcher remains alive, and Canvas shows Automations Unavailable (#18299). Restore the cold backup, restart and doctor before continuing. Never apply this fixture to an operator database.

Occupied internal frontend portF26.cli-port-in-useKnown failure

Scenario F26--occupied-internal-frontend-port · Permalink · Source

Given

Use F26’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

  1. Do
    Reserve a loopback frontend port in an owned listener and run the genuine launcher with separate free ingress/backend/automation ports. The reported default-port branch can also be observed read-only when port 3001 is already occupied; do not stop its owner. Expected: an occupied internal frontend port receives a free fallback while explicit service-port conflicts keep their existing semantics. Known failure, reproduced 2026-10-12: agent-canvas --port 18940 exits 1 with frontend: port 3001, despite the ingress override (#17546, #16226). An explicit OH_CANVAS_SAFE_VITE_PORT is a current workaround; it does not provide automatic fallback.

Expect

an occupied ingress (or service) port stops the launch with Cannot start: the following ports are already in use naming each busy port.

Known bug observations

Known failure, reproduced 2026-10-12: agent-canvas --port 18940 exits 1 with frontend: port 3001, despite the ingress override (#17546, #16226). An explicit OH_CANVAS_SAFE_VITE_PORT is a current workaround; it does not provide automatic fallback.

Exact CLI recipe & bug checks

Reserve a loopback frontend port in an owned listener and run the genuine launcher with separate free ingress/backend/automation ports. The reported default-port branch can also be observed read-only when port 3001 is already occupied; do not stop its owner. Expected: an occupied internal frontend port receives a free fallback while explicit service-port conflicts keep their existing semantics. Known failure, reproduced 2026-10-12: agent-canvas --port 18940 exits 1 with frontend: port 3001, despite the ingress override (#17546, #16226). An explicit OH_CANVAS_SAFE_VITE_PORT is a current workaround; it does not provide automatic fallback.

Required frontend deathF26.runtime-servicesKnown failureKnown issue

Scenario F26--required-frontend-death · Permalink · Source

Given

Use F26’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

  1. Do
    After a healthy private launch, identify only its required static-server PID using launcher ancestry plus its owned frontend port, then send that child SIGKILL. Open / and check the owned launcher's liveness and authenticated /api/settings. Expected: an unexpected required-service death stops the stack and reports failure. Known failure, reproduced 2026-10-12: the launcher and backend remain alive, settings returns 200, and the main UI returns 502 Bad Gateway (#16927). This live trigger is SIGKILL, not a port-race EADDRINUSE; the failing required-child exit branch is shared. Restart and doctor to restore the run.

Expect

/server_info.runtime_services lists the agent-server, ingress, frontend and automation URLs as the agent sees them, and a new conversation's system prompt carries a matching <RUNTIME_SERVICES> block.

Known bug observations

Known failure, reproduced 2026-10-12: the launcher and backend remain alive, settings returns 200, and the main UI returns 502 Bad Gateway (#16927). This live trigger is SIGKILL, not a port-race EADDRINUSE; the failing required-child exit branch is shared. Restart and doctor to restore the run.

Linked known issues

Known issue #16720 (F26.runtime-services): CPU-heavy commands in several conversations can starve Agent Server and stop the UI loading; the report requests lower priority for agent commands than the service. Recheck with a disposable bounded workload, recorded CPU/resource limits and real command observations, never arbitrary stress on the shared machine. The exact Rust workload and affected resource envelope are missing, and real agent execution needs a model key. The backend counterpart is OpenHands/software-agent-sdk#4575.

Known issue #16690 (F26.docker-image F26.runtime-services): A Docker deployment with nine chats becomes slow/disconnected, then reloads lose the list and startup proxies report ECONNREFUSED. Recheck on the affected Windows-host Docker configuration with retained conversation state and a controlled workload; creating a few empty local chats cannot prove the reported resource/runtime failure. This run lacks Docker and the original conversation/workload fixture.

Known issue #16300 (F26.runtime-services): The npm launcher can fail while building LiteLLM native dependencies when resolved AWS crates require a newer Rust compiler, leaving backend services unavailable. Recheck an isolated uncached uv environment with litellm==1.95.0, the reported Rust 1.93.1/toolchain/platform and dependency resolution; a warm current backend cache cannot exercise that install failure. The historical dependency/toolchain fixture is unavailable.

Known issue #16208 (F26.runtime-services): On Fedora Rawhide/Python 3.14.6, settings work but starting any conversation returns HTTP 500. Recheck a native Fedora environment with the reported SDK/runtime and model/profile, including the underlying server traceback; this macOS current-runtime stack cannot prove that platform-specific failure.

Known issue OpenHands/software-agent-sdk#4901 (F26.runtime-services): closing an ACP conversation on POSIX is reported to leave the adapter's descendant Node/CLI processes alive after the top npm process exits. Recheck an owned authenticated ACP provider command chain, record its exact descendant identities before close and inspect only those identities afterwards; cleanup must target those owned groups. The current run has no authorized authenticated ACP runtime for this path.

Known issue OpenHands/software-agent-sdk#5974 (F26.runtime-services; workspace picker in F03): a saved-workspace mutation waiting for its registry file lock can block the Agent Server event loop, including /alive, until the lock is released. Recheck only an owned registry using a bounded lock holder, an independently confirmed waiting mutation, concurrent liveness probes and a successful post-release readback; the original evidence uses Linux kernel lock-wait readback. A healthy idle server or concurrent writes without deliberate lock contention do not cover it.

Exact CLI recipe & bug checks

After a healthy private launch, identify only its required static-server PID using launcher ancestry plus its owned frontend port, then send that child SIGKILL. Open / and check the owned launcher's liveness and authenticated /api/settings. Expected: an unexpected required-service death stops the stack and reports failure. Known failure, reproduced 2026-10-12: the launcher and backend remain alive, settings returns 200, and the main UI returns 502 Bad Gateway (#16927). This live trigger is SIGKILL, not a port-race EADDRINUSE; the failing required-child exit branch is shared. Restart and doctor to restore the run.

Static-server credential argvF26.runtime-servicesKnown failureKnown issue

Scenario F26--static-server-credential-argv · Permalink · Source

Given

Use F26’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

  1. Do
    Inspect only this run's owned static-server process for the frontend port. Read the private key in memory and retain booleans indicating whether --session-api-key is present and whether that literal key occurs in argv; never print argv or the key. Expected: other host users cannot recover the session key from command-line arguments. Known failure, reproduced 2026-10-12: both booleans are true for the owned static frontend (#16383). Docker remains a separate unavailable prerequisite here.

Expect

/server_info.runtime_services lists the agent-server, ingress, frontend and automation URLs as the agent sees them, and a new conversation's system prompt carries a matching <RUNTIME_SERVICES> block.

Known bug observations

Known failure, reproduced 2026-10-12: both booleans are true for the owned static frontend (#16383). Docker remains a separate unavailable prerequisite here.

Linked known issues

Known issue #16720 (F26.runtime-services): CPU-heavy commands in several conversations can starve Agent Server and stop the UI loading; the report requests lower priority for agent commands than the service. Recheck with a disposable bounded workload, recorded CPU/resource limits and real command observations, never arbitrary stress on the shared machine. The exact Rust workload and affected resource envelope are missing, and real agent execution needs a model key. The backend counterpart is OpenHands/software-agent-sdk#4575.

Known issue #16690 (F26.docker-image F26.runtime-services): A Docker deployment with nine chats becomes slow/disconnected, then reloads lose the list and startup proxies report ECONNREFUSED. Recheck on the affected Windows-host Docker configuration with retained conversation state and a controlled workload; creating a few empty local chats cannot prove the reported resource/runtime failure. This run lacks Docker and the original conversation/workload fixture.

Known issue #16300 (F26.runtime-services): The npm launcher can fail while building LiteLLM native dependencies when resolved AWS crates require a newer Rust compiler, leaving backend services unavailable. Recheck an isolated uncached uv environment with litellm==1.95.0, the reported Rust 1.93.1/toolchain/platform and dependency resolution; a warm current backend cache cannot exercise that install failure. The historical dependency/toolchain fixture is unavailable.

Known issue #16208 (F26.runtime-services): On Fedora Rawhide/Python 3.14.6, settings work but starting any conversation returns HTTP 500. Recheck a native Fedora environment with the reported SDK/runtime and model/profile, including the underlying server traceback; this macOS current-runtime stack cannot prove that platform-specific failure.

Known issue OpenHands/software-agent-sdk#4901 (F26.runtime-services): closing an ACP conversation on POSIX is reported to leave the adapter's descendant Node/CLI processes alive after the top npm process exits. Recheck an owned authenticated ACP provider command chain, record its exact descendant identities before close and inspect only those identities afterwards; cleanup must target those owned groups. The current run has no authorized authenticated ACP runtime for this path.

Known issue OpenHands/software-agent-sdk#5974 (F26.runtime-services; workspace picker in F03): a saved-workspace mutation waiting for its registry file lock can block the Agent Server event loop, including /alive, until the lock is released. Recheck only an owned registry using a bounded lock holder, an independently confirmed waiting mutation, concurrent liveness probes and a successful post-release readback; the original evidence uses Linux kernel lock-wait readback. A healthy idle server or concurrent writes without deliberate lock contention do not cover it.

Exact CLI recipe & bug checks

Inspect only this run's owned static-server process for the frontend port. Read the private key in memory and retain booleans indicating whether --session-api-key is present and whether that literal key occurs in argv; never print argv or the key. Expected: other host users cannot recover the session key from command-line arguments. Known failure, reproduced 2026-10-12: both booleans are true for the owned static frontend (#16383). Docker remains a separate unavailable prerequisite here.

Keep-alive proxy timeout listenersF26.runtime-servicesKnown failureKnown issue

Scenario F26--keep-alive-proxy-timeout-listeners · Permalink · Source

Given

Use F26’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

  1. Do
    Through the owned ingress, send 20 sequential authenticated /api/conversations/search?limit=1 requests on one HTTP keep-alive connection, recording response statuses and the reused local socket port, then inspect only the owned ingress's warning line. Expected: one socket does not acquire one timeout listener per request. Known failure, reproduced 2026-10-12: all 20 requests are 200 on the same socket and ingress emits MaxListenersExceededWarning for 11 timeout listeners (#16839). /server_info uses a custom enrichment handler here, so it is not the generic proxy branch. Keep header values and full logs private.

Expect

/server_info.runtime_services lists the agent-server, ingress, frontend and automation URLs as the agent sees them, and a new conversation's system prompt carries a matching <RUNTIME_SERVICES> block.

Known bug observations

Known failure, reproduced 2026-10-12: all 20 requests are 200 on the same socket and ingress emits MaxListenersExceededWarning for 11 timeout listeners (#16839). /server_info uses a custom enrichment handler here, so it is not the generic proxy branch. Keep header values and full logs private.

Linked known issues

Known issue #16720 (F26.runtime-services): CPU-heavy commands in several conversations can starve Agent Server and stop the UI loading; the report requests lower priority for agent commands than the service. Recheck with a disposable bounded workload, recorded CPU/resource limits and real command observations, never arbitrary stress on the shared machine. The exact Rust workload and affected resource envelope are missing, and real agent execution needs a model key. The backend counterpart is OpenHands/software-agent-sdk#4575.

Known issue #16690 (F26.docker-image F26.runtime-services): A Docker deployment with nine chats becomes slow/disconnected, then reloads lose the list and startup proxies report ECONNREFUSED. Recheck on the affected Windows-host Docker configuration with retained conversation state and a controlled workload; creating a few empty local chats cannot prove the reported resource/runtime failure. This run lacks Docker and the original conversation/workload fixture.

Known issue #16300 (F26.runtime-services): The npm launcher can fail while building LiteLLM native dependencies when resolved AWS crates require a newer Rust compiler, leaving backend services unavailable. Recheck an isolated uncached uv environment with litellm==1.95.0, the reported Rust 1.93.1/toolchain/platform and dependency resolution; a warm current backend cache cannot exercise that install failure. The historical dependency/toolchain fixture is unavailable.

Known issue #16208 (F26.runtime-services): On Fedora Rawhide/Python 3.14.6, settings work but starting any conversation returns HTTP 500. Recheck a native Fedora environment with the reported SDK/runtime and model/profile, including the underlying server traceback; this macOS current-runtime stack cannot prove that platform-specific failure.

Known issue OpenHands/software-agent-sdk#4901 (F26.runtime-services): closing an ACP conversation on POSIX is reported to leave the adapter's descendant Node/CLI processes alive after the top npm process exits. Recheck an owned authenticated ACP provider command chain, record its exact descendant identities before close and inspect only those identities afterwards; cleanup must target those owned groups. The current run has no authorized authenticated ACP runtime for this path.

Known issue OpenHands/software-agent-sdk#5974 (F26.runtime-services; workspace picker in F03): a saved-workspace mutation waiting for its registry file lock can block the Agent Server event loop, including /alive, until the lock is released. Recheck only an owned registry using a bounded lock holder, an independently confirmed waiting mutation, concurrent liveness probes and a successful post-release readback; the original evidence uses Linux kernel lock-wait readback. A healthy idle server or concurrent writes without deliberate lock contention do not cover it.

Exact CLI recipe & bug checks

Through the owned ingress, send 20 sequential authenticated /api/conversations/search?limit=1 requests on one HTTP keep-alive connection, recording response statuses and the reused local socket port, then inspect only the owned ingress's warning line. Expected: one socket does not acquire one timeout listener per request. Known failure, reproduced 2026-10-12: all 20 requests are 200 on the same socket and ingress emits MaxListenersExceededWarning for 11 timeout listeners (#16839). /server_info uses a custom enrichment handler here, so it is not the generic proxy branch. Keep header values and full logs private.

Default Rich logging responsivenessF26.remote-backendKnown failureKnown issue

Scenario F26--default-rich-logging-responsiveness · Permalink · Source

Given

Use F26’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

  1. Do
    Temporarily replace only an owned Agent Server with its same pinned interpreter/state on the same port; unset LOG_JSON, CI, GITHUB_ACTIONS and LOG_RICH_TRACEBACKS, and retain its PID for cleanup. Create a bare-tool conversation whose dummy LLM points at closed http://127.0.0.1:9/v1 with zero retries. Start 60 direct /alive probes concurrently with a genuine Canvas Send message; inspect the rendered connection error and final conversation status. Expected: every probe stays below 1.5 s during failure. Known failure, reproduced 2026-10-12 on Agent Server 1.54.0: the conversation ends error and one health probe stalls 2.091 s (OpenHands/software-agent-sdk#5633). This is a finite connection-error case, separate from the unprovided cyclic websocket fixture in SDK #5373. Stop that owned replacement, restore the normal launcher and doctor.

Expect

from a normal Canvas, backend selector → Add Backend → Agent-server → Remote shows the self-hosting guidance and connects to another instance (stand-in for a self-hosted VM, docs/SELF_HOSTING.md).

Known bug observations

Known failure, reproduced 2026-10-12 on Agent Server 1.54.0: the conversation ends error and one health probe stalls 2.091 s (OpenHands/software-agent-sdk#5633). This is a finite connection-error case, separate from the unprovided cyclic websocket fixture in SDK #5373. Stop that owned replacement, restore the normal launcher and doctor.

Linked known issues

Known issue #15434 (F26.remote-backend): The embedded VS Code action on a self-hosted reverse-proxy origin was reported to reopen Canvas because its advertised URL omitted the separate editor port/path. Current launchers include an ingress /vscode/ route; recheck against the affected HTTPS proxy deployment with an actually available editor binary and emitted URL before concluding resolution. This run lacks that self-hosted proxy/editor deployment.

Known issue OpenHands/software-agent-sdk#5373 (F26.remote-backend): A self-hosted Agent Server using default Rich traceback logging can wedge its event loop and grow memory when a websocket error has a cyclic/deep exception chain; Canvas then times out or disconnects. The npm launcher forces JSON logging, so rechecking needs a separately hosted default-Rich backend plus the reported pathological peer/backlog reproducer and bounded memory/time isolation. That original peer/driver is not included in the issue body; do not synthesize a server patch or stress the shared host and claim a live UI reproduction.

Exact CLI recipe & bug checks

Temporarily replace only an owned Agent Server with its same pinned interpreter/state on the same port; unset LOG_JSON, CI, GITHUB_ACTIONS and LOG_RICH_TRACEBACKS, and retain its PID for cleanup. Create a bare-tool conversation whose dummy LLM points at closed http://127.0.0.1:9/v1 with zero retries. Start 60 direct /alive probes concurrently with a genuine Canvas Send message; inspect the rendered connection error and final conversation status. Expected: every probe stays below 1.5 s during failure. Known failure, reproduced 2026-10-12 on Agent Server 1.54.0: the conversation ends error and one health probe stalls 2.091 s (OpenHands/software-agent-sdk#5633). This is a finite connection-error case, separate from the unprovided cyclic websocket fixture in SDK #5373. Stop that owned replacement, restore the normal launcher and doctor.

Desktop macos titlebarF26.desktop-macos-titlebarBlocked

Scenario F26--contract-f26-desktop-macos-titlebar · Permalink · Source

Given

Use F26’s prerequisites and the common setup. Follow the family’s order when a script uses an earlier fixture or says “as above”.

Do

This behavior has no separately named recipe in this snapshot. Use the family’s entry points and the contract below; the page does not invent a replay.

Expect

on macOS the desktop window hides the native title bar and reserves a 28 px drag band (titlebar-drag-region, aria-hidden) above the shell, so the sidebar logo clears the traffic lights and the window can be dragged; the band is dropped in native fullscreen, also after a reload while fullscreen. Linux and Windows windows and browser tabs render no band (window.desktopShell is undefined in a tab; platform is linux on Linux). The band is rendered by the root layout, its error shell and the config-loading spinner, not by the first-run onboarding screen. Blocked without macOS (#17474).

Family notes, prerequisites for known issues & limits Known failureKnown issueBlocked
  • control-openhands launch covers only the full stack and --public. Partial stacks and --host need a hand-started launcher with its own HOME, OH_CANVAS_SAFE_STATE_DIR and all three OH_CANVAS_SAFE_*_PORT variables; without them it writes into the real ~/.openhands/agent-canvas and grabs the default ports 8000/18000/18001/3001 that another agent may be using.
  • A hand-started launcher's first start downloads the agent-server into its private HOME cache (30–90 s). Prefix assignments are expanded left to right, so HOME=$Q/home UV_CACHE_DIR=$HOME/.cache/uv points the cache into $Q/home too, not at your real cache. Reuse one $Q for repeat launches to keep it warm.
  • The browser daemon refuses other origins unless you pass --allow-external to browser goto. Each port is its own origin, so a partial-stack page starts at first run again (onboarding and a consent dialog), independent of your run's onboarding.
  • In a frontend-only UI the Add a backend step's Next is enabled with an empty key, while on a LAN or --public stack it is disabled until a key is typed: the host the step probes differs (503 vs. auth-required).
  • A partial-stack origin keeps its stored backend in the browser profile across launches, so a second pass lands straight in the shell. control-openhands browser reset brings back the first run on every origin; then control-openhands browser goto / and control-openhands onboard --skip restore your own run's baseline.
  • Which screen a frontend-only origin shows depends on the browser, not the launcher: a profile new to the origin gets Add a backend (first run owns the initial backend), while a profile that stores a backend for that origin (the default-local entry a full launcher on the same port seeded, or any backend added there earlier) gets the recovery gate of F25.recovery-gate as soon as that backend answers 503 (F26.frontend-only-returning). The no-toast check belongs to the first-run state only: on the gate, browser toasts --history lists generic An error occurred status toasts raised by the failing 503 probes (two within 12 s here), with no hint of which request failed. Known failure (reproduced 2026-10-08): both toasts come from the free-models hydrator's GET /api/llm/models/verified 503, which no meta.disableToast guards on that screen (nor does backend-version.tsx); #18160.
  • Stopping a backend-only instance while the frontend-only page that uses it stays open is the same backend-down state as F25.recovery-gate after a reload; keep the returning-user bullet above as the frontend-only-specific check (it reproduces the default-local case).
  • The launcher serves the bundled VS Code editor. #17660 made that opt-in (OH_CANVAS_ENABLE_VSCODE=true) and #18048 reverted it, so only a checkout between the two needs the variable (launch --vscode sets it). A launcher that serves it reserves /vscode/: a frontend-only stack answers 503 there and logs [static] /vscode -> 503 (rejected); a stack with an agent-server lists /vscode → http://127.0.0.1:<agent-server port + 1000> in its ingress banner and hands the agent-server that editor port (19961 for the hand-started launchers here, 19000 for the desktop app), which the launcher's own port check does not cover. On a checkout between the two without the variable, /vscode/ is an ordinary SPA URL (200). Here the agent-server has no editor binary (VSCode server binary not found, VSCode will be disabled in the launcher log, /api/vscode/status {"running":false,"enabled":true}), so the editor port stays free even then.
  • The port-in-use error is printed twice, the second time with a Node stack trace, and suggests PORT=<other> rather than --port (#17949). The --public key error's example says npm run dev -- --public instead of the npx command.
  • A backend-only instance's /server_info.runtime_services still describes the ingress as routing /* to the frontend, which it does not run (#17949).
  • In this sandbox node_modules/electron/dist is absent and node node_modules/electron/install.js fails with ERR_ASSERTION (2026-10-13), so the F26.desktop-* rows stay blocked here.
  • The Electron app hardcodes ports 8000/18000/18001 (and the static server takes 3001), ignores PORT, and refuses to start as root without --no-sandbox (Running as root without --no-sandbox is not supported). npm run desktop first runs build:app, which rebuilds build/ under every other run: start node_modules/.bin/electron --no-sandbox electron directly against the existing build.
  • Never stop the desktop app with one group-wide signal: when Xvfb dies with Electron, the app exits without stopping its backend, which is left orphaned (own process groups, parent 1) on 8000/18000/18001/3001 and blocks the next start. If that happens, find the orphans by their ports' command lines (ps -eo pid,pgid,cmd, look for --port 18000, --port 18001, --port 3001, ingress.mjs --port 8000) and stop each group by id.
  • The generated session key lives in $HOME/.openhands/agent-canvas/api-key.txt, not under OH_CANVAS_SAFE_STATE_DIR: a hand-started launcher without a private HOME reads and writes the operator's real key file.
  • A browser origin keeps the backends it stored on an earlier pass: after the LAN pass on 18960, a later loopback launch on 18960 shows the stale QA_LAN backend (red, old key) instead of onboarding, although the page injects a valid key. Assert key-injection bullets through the window globals, or browser reset first.
  • Electron's splash shows each raw service-log line (agent-server: {"asctime": ...) as its status text during boot; the named phases only appear between them.
  • build:lib from a copy whose node_modules is a symlink emits bundled dependencies under dist/<absolute node_modules path>/...; that path is an artifact of the copy, not of the product.
  • **Bug candidate (F26.host-bind-lan-optin):** scripts/bind-host.mjs tells npm users to pass --allow-lan-session-key, but neither bin/agent-canvas.mjs nor scripts/dev-with-automation.mjs parses or forwards it (only scripts/static-server.mjs and the Docker entrypoint do), and unknown flags are silently ignored (#17949, whose first item is exactly this; reproduced again on 2026-10-08 at d2c89252d).
  • Known issue #17946: the npm launcher never creates its TMUX_TMPDIR, so hand-started instances share one tmux server and a restart of one resets the others' terminals.
  • Known issue #17763 (F26.docker-image): The Docker entrypoint can leave protected Agent Server APIs unauthenticated when only LOCAL_BACKEND_API_KEY is supplied, because its export to OH_SESSION_API_KEYS_0 is inside the generate-key branch. Recheck an owned container with a dummy custom key: unauthenticated GET /api/conversations/search must be 401 and the same request with that dummy key 200. /server_info is intentionally public and is not an auth probe. Blocked here without a Docker daemon.
  • Known issue #17623 (F26.helm-chart): The chart defaults service-account-token automounting to true even with RBAC disabled, exposing an unused cluster credential to agent processes. Recheck a disposable Kubernetes installation using the default values and an explicit opt-in; inspect token existence/readability only, never its contents. Blocked without Helm and a Kubernetes cluster.
  • Known issue #17470 (F26.docker-image): On Apple Silicon under Colima/Virtualization.framework, the image's Python services can exit with SIGILL while the frontend leaves onboarding at Disconnected. The report isolates import litellm and compares OPENSSL_armcap=0; recheck on the affected ARM/Colima guest with the reported cryptography/OpenSSL combination. A native macOS npm stack is not that container environment.
  • Known issue #17112 (F26.desktop-main-window F26.desktop-external-links): Desktop main-window navigation, an OAuth popup redirect, or a nested popup can render a remote page inside the app frame. Recheck all three on an owned running Electron shell with a harmless remote page and loopback redirect fixture; the browser harness cannot operate Electron windows and the installed Electron binary is unavailable here.
  • Known issue #16872 (F26.desktop-external-links): Non-browser URI schemes were reported to reach the OS handler from the desktop popup flow. Current source now parses hosts and restricts external protocols; recheck refusal in a running owned Electron shell before concluding resolution. Observe an inert denied test scheme without invoking any real OS handler. The browser harness cannot drive Electron and no installed Electron binary is available here.
  • Known issue #16774 (F26.desktop-boot-splash F26.desktop-main-window): The macOS 1.14.0 DMG installs an app that Gatekeeper reports as damaged instead of booting. Recheck the actual downloaded signed/notarized release artifact through install and first launch on macOS; starting source Electron or removing quarantine would bypass the reported failure. That release DMG/signature fixture is unavailable in this run.
  • Known issue #16720 (F26.runtime-services): CPU-heavy commands in several conversations can starve Agent Server and stop the UI loading; the report requests lower priority for agent commands than the service. Recheck with a disposable bounded workload, recorded CPU/resource limits and real command observations, never arbitrary stress on the shared machine. The exact Rust workload and affected resource envelope are missing, and real agent execution needs a model key. The backend counterpart is OpenHands/software-agent-sdk#4575.
  • Known issue #16690 (F26.docker-image F26.runtime-services): A Docker deployment with nine chats becomes slow/disconnected, then reloads lose the list and startup proxies report ECONNREFUSED. Recheck on the affected Windows-host Docker configuration with retained conversation state and a controlled workload; creating a few empty local chats cannot prove the reported resource/runtime failure. This run lacks Docker and the original conversation/workload fixture.
  • Known issue #16511 (F26.docker-image): The documented Docker bind mount can be unwritable to container uid 10001, killing Agent Server/automation while the frontend still serves Bad Gateway. Recheck a disposable host-owned mode-700 mount and a writable control against an owned image; never chmod the operator's real state directory. Blocked here without Docker.
  • Known issue #16300 (F26.runtime-services): The npm launcher can fail while building LiteLLM native dependencies when resolved AWS crates require a newer Rust compiler, leaving backend services unavailable. Recheck an isolated uncached uv environment with litellm==1.95.0, the reported Rust 1.93.1/toolchain/platform and dependency resolution; a warm current backend cache cannot exercise that install failure. The historical dependency/toolchain fixture is unavailable.
  • Known issue #16208 (F26.runtime-services): On Fedora Rawhide/Python 3.14.6, settings work but starting any conversation returns HTTP 500. Recheck a native Fedora environment with the reported SDK/runtime and model/profile, including the underlying server traceback; this macOS current-runtime stack cannot prove that platform-specific failure.
  • Known issue #15434 (F26.remote-backend): The embedded VS Code action on a self-hosted reverse-proxy origin was reported to reopen Canvas because its advertised URL omitted the separate editor port/path. Current launchers include an ingress /vscode/ route; recheck against the affected HTTPS proxy deployment with an actually available editor binary and emitted URL before concluding resolution. This run lacks that self-hosted proxy/editor deployment.
  • Known issue OpenHands/software-agent-sdk#5373 (F26.remote-backend): A self-hosted Agent Server using default Rich traceback logging can wedge its event loop and grow memory when a websocket error has a cyclic/deep exception chain; Canvas then times out or disconnects. The npm launcher forces JSON logging, so rechecking needs a separately hosted default-Rich backend plus the reported pathological peer/backlog reproducer and bounded memory/time isolation. That original peer/driver is not included in the issue body; do not synthesize a server patch or stress the shared host and claim a live UI reproduction.
  • Known issue OpenHands/software-agent-sdk#5310 (F26.docker-conversation-runtime): A Docker conversation can lose the auxiliary title profile at the runtime boundary, leaving an ACP conversation untitled or producing an unrelated title-auth error while the agent still works. Recheck a saved reachable title profile and an ACP conversation inside an owned per-conversation Docker runtime, comparing its title/profile metadata with the host; inspect only profile names and credential-presence booleans. Blocked without Docker and real auxiliary-title/ACP credentials.
  • Known issue OpenHands/software-agent-sdk#5307 (F26.docker-conversation-runtime): New worktree on a Docker-backed conversation can create git metadata whose reverse path exists only inside the container, so the still-live host worktree reads prunable gitdir file points to non-existent location. Recheck a disposable real git repository mounted into an owned conversation container, comparing host and container git worktree list/status while it is alive; a local worktree does not test the filesystem boundary. Blocked without Docker.
  • Known issue OpenHands/software-agent-sdk#4901 (F26.runtime-services): closing an ACP conversation on POSIX is reported to leave the adapter's descendant Node/CLI processes alive after the top npm process exits. Recheck an owned authenticated ACP provider command chain, record its exact descendant identities before close and inspect only those identities afterwards; cleanup must target those owned groups. The current run has no authorized authenticated ACP runtime for this path.
  • Known issue OpenHands/software-agent-sdk#5974 (F26.runtime-services; workspace picker in F03): a saved-workspace mutation waiting for its registry file lock can block the Agent Server event loop, including /alive, until the lock is released. Recheck only an owned registry using a bounded lock holder, an independently confirmed waiting mutation, concurrent liveness probes and a successful post-release readback; the original evidence uses Linux kernel lock-wait readback. A healthy idle server or concurrent writes without deliberate lock contention do not cover it.
  • Known issue OpenHands/software-agent-sdk#5731 (F26.cli-public-needs-key): whitespace-only session keys remain configured credentials in the Agent Server. The Canvas launcher forwards a nonempty LOCAL_BACKEND_API_KEY verbatim, so a whitespace-only deployment value can leave the public login unable to use its intended key. Recheck an owned loopback-only public deployment with a whitespace-only supplied key, its actual HTTP/WebSocket authentication and a normal nonblank control; never test the server's default broad bind on an exposed interface. The SDK's missing-OH_SECRET_KEY cipher fallback is a separate configuration branch from a Canvas launcher that supplies an encryption key.

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.