Skip to content

Move per-conversation metadata from localStorage to agent-server tags#1289

Draft
chuckbutkus wants to merge 1 commit into
mainfrom
deprecate-conversation-metadata-store
Draft

Move per-conversation metadata from localStorage to agent-server tags#1289
chuckbutkus wants to merge 1 commit into
mainfrom
deprecate-conversation-metadata-store

Conversation

@chuckbutkus

@chuckbutkus chuckbutkus commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

H:

  • A human has tested these changes.

AGENT:

This PR was created by an AI agent (OpenHands) on behalf of the user. Per the AGENTS.md "PR Description Human Check" policy, only humans may edit the H: section or check the human-tested checkbox.

End-to-end verification ran in the sandbox:

  • npm run lint — typecheck + ESLint + Prettier all clean
  • npx vitest run --reporter=dot414 files, 3143 tests passing, 12 skipped, 9 todo (no regressions vs. main)
  • New focused test file __tests__/api/conversation-metadata-migration.test.ts (5/5 pass) covers the migration helper end-to-end

I did not exercise the Playwright mock-LLM or live E2E suites in this sandbox; CI will run them.


Why

This is option 5 from the localStorage-elimination cleanup tracked in repo notes:

Extend the conversation record server-side with selected_workspace + active_profile; deprecate conversation-metadata-store.ts.

The frontend was stashing per-conversation metadata (selected_repository, selected_branch, git_provider, selected_workspace, active_profile) in a single localStorage blob (openhands-agent-server-conversation-metadata). That has two well-known problems:

  1. It does not roam — switching machines or browsers loses the metadata.
  2. The chat-header LLM profile switcher ([Bug]: Conversations start with the wrong model #1082) and the Files tab diff-view default both rely on it, so any user who hit a fresh browser saw degraded UX.

The agent-server already supports an arbitrary tags: Record<str, str> map per conversation. The only blocker was the validator regex ^[a-z0-9]+$, which rejected the snake_case keys we want — that is fixed in the paired SDK PR.

Summary

  • Move the five metadata fields onto the agent-server's per-conversation tags map; the SDK paired PR (Allow underscores in conversation tag keys software-agent-sdk#3621) relaxes the tag-key regex to ^[a-z0-9_]+$ so snake_case keys validate.
  • POST /api/conversations now stamps the metadata as tags via mergeMetadataIntoTags. Two new PATCH paths cover incremental updates: updateConversationRepository (existing; now tag-based) and updateConversationActiveProfile (new). Both preserve unrelated tags like acpserver.
  • toAppConversation reads from server tags first and falls back to the legacy localStorage entry; conversation-metadata-store.ts is @deprecated but kept alive for that fallback. A new conversation-metadata-migration.ts runs inside usePaginatedConversations's queryFn and lazily PATCHes legacy entries onto missing tags, then clears the localStorage entry.

Issue Number

Pairs with OpenHands/software-agent-sdk#3621 (must merge first so the relaxed ^[a-z0-9_]+$ regex is published).

How to Test

  1. git checkout deprecate-conversation-metadata-store
  2. npm ci
  3. npm run lint — should be green
  4. npx vitest run — should be green (414 files, 3143 tests pass)
  5. Manual smoke against a live agent-server built from SDK PR #3621:
    • Create a fresh local conversation with no repo / no workspace; reload. The chat header should still show the active LLM profile name (the active_profile server tag survives reload).
    • Create a conversation with a local workspace attached; open the Files tab. It should default to diff view (relies on selected_workspace server tag → useHasAttachedSource).
    • Create a conversation via the cloud-style repo picker; verify the sidebar shows the repo/branch/provider after reload (relies on selected_repository / selected_branch / git_provider server tags).
    • Manually seed the legacy localStorage blob (localStorage.setItem('openhands-agent-server-conversation-metadata', JSON.stringify({ 'conv-xyz': { selected_repository: 'octocat/hello-world', selected_branch: 'main', git_provider: 'github' }}))) for an existing conversation, reload the conversation list, then watch the network tab: a PATCH against /api/conversations/conv-xyz with tags is fired and the localStorage entry is cleared.

Video/Screenshots

N/A — pure refactor of the persistence boundary, no visible UI change.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • Dependency on SDK PR #3621. The five tag keys (selected_repository, selected_branch, git_provider, selected_workspace, active_profile) all contain underscores and would 422 against the old ^[a-z0-9]+$ validator. Do not merge this PR before the SDK PR is released into the agent-server version the frontend pins to.
  • Graceful rollout. Existing localStorage entries on user machines are still readable (toAppConversation falls back to the legacy store) and are actively migrated on each conversation list refresh, so users will not lose metadata across the upgrade. Once one upgrade cycle has passed and the legacy blob is no longer expected to exist in the wild, src/api/conversation-metadata-store.ts, the fallback branches in toAppConversation, and src/api/conversation-metadata-migration.ts can all be deleted.
  • Wire shape. Conversations with no metadata and no ACP server send no tags field at all (same as before). Conversations with at least one field send a tags map containing only the keys with non-empty values, merged with the pre-existing acpserver tag when applicable.

@chuckbutkus can click here to continue refining the PR


🐳 Docker images for this PR

GHCR package: https://github.com/OpenHands/agent-canvas/pkgs/container/agent-canvas

Component Value
Image ghcr.io/openhands/agent-canvas
Architectures amd64, arm64
Agent Server ghcr.io/openhands/agent-server:1.27.0-python
Automation openhands-automation==1.0.0a6
Commit 77596e758cf0547c99daee19f24abdd6284966fc

Pull (multi-arch manifest)

# Multi-arch manifest — Docker automatically pulls the correct architecture
docker pull ghcr.io/openhands/agent-canvas:sha-77596e7

Run

docker run -it --rm \
  -p 8000:8000 \
  ghcr.io/openhands/agent-canvas:sha-77596e7

All tags pushed for this build

ghcr.io/openhands/agent-canvas:sha-77596e7-amd64
ghcr.io/openhands/agent-canvas:deprecate-conversation-metadata-store-amd64
ghcr.io/openhands/agent-canvas:pr-1289-amd64
ghcr.io/openhands/agent-canvas:sha-77596e7-arm64
ghcr.io/openhands/agent-canvas:deprecate-conversation-metadata-store-arm64
ghcr.io/openhands/agent-canvas:pr-1289-arm64
ghcr.io/openhands/agent-canvas:sha-77596e7
ghcr.io/openhands/agent-canvas:deprecate-conversation-metadata-store
ghcr.io/openhands/agent-canvas:pr-1289

About Multi-Architecture Support

  • Each tag (e.g., sha-77596e7) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., sha-77596e7-amd64) are also available if needed

Eliminate the `openhands-agent-server-conversation-metadata` localStorage
blob. The five fields it held (selected_repository, selected_branch,
git_provider, selected_workspace, active_profile) now ride on each
conversation's server-side `tags` map, with the agent-server as the
source of truth.

Pairs with software-agent-sdk PR #3621, which relaxes the tag-key regex
from `^[a-z0-9]+$` to `^[a-z0-9_]+$` so snake_case keys validate.

Wire-shape changes:
- POST /api/conversations now sends the five metadata fields as `tags`
  alongside the existing `acpserver` tag (via `mergeMetadataIntoTags`).
- PATCH /api/conversations/{id} is used to update single fields:
  `updateConversationRepository` (existing, now tag-based) and the new
  `updateConversationActiveProfile`. Both preserve unrelated tags.
- `AppConversation` keeps the same flat shape; `toAppConversation`
  reads from server tags first and falls back to legacy localStorage
  for un-migrated conversations.

Migration:
- `conversation-metadata-store.ts` is now `@deprecated` and only
  retained for the read-side fallback above.
- A new `conversation-metadata-migration.ts` runs lazily inside the
  `usePaginatedConversations` queryFn: for each conversation in the
  current page, if a legacy localStorage entry exists, PATCH any
  fields the server is missing onto its tags and clear the entry.
  Best-effort and retried on the next list refresh on failure.

Tests:
- New `__tests__/api/conversation-metadata-migration.test.ts` covers
  the migration helper (PATCH-and-clear, no-op when mirrored, preserve
  unrelated tags, retry on failure, empty-list no-op).
- Rewrote `use-switch-llm-profile-and-log` and conversation-websocket
  tests to assert against `updateConversationActiveProfile` calls
  instead of localStorage state.
- Updated `use-create-conversation-metadata` to assert against the
  new POST tags payload; remaining create-conversation call-shape
  tests pick up the new `active_profile` field on the metadata arg.

AGENTS.md: dedupe Files-tab note, add migration note.

Co-authored-by: openhands <openhands@all-hands.dev>
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-canvas Ready Ready Preview, Comment Jun 10, 2026 4:29am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

❌ Mock-LLM E2E Tests

36/54 passed · 9 failed · 9 skipped

Commit: 77596e75 · Workflow run · Test artifacts

Status Test Duration
mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 1: configure ACP agent via Settings → Agent UI 13.7s
mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 2: reload and verify ACP settings are persisted in UI 5.6s
mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 3: start ACP conversation and verify agent reply 36.2s
⏭️ mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 4: resume ACP conversation from sidebar after navigating away 0ms
mock-llm-auth-modes.spec.ts › auth mode: fresh install with runtime-injected key › reaches the onboarding modal without pre-seeded localStorage 1.5s
mock-llm-auth-modes.spec.ts › auth mode: non-public key rotation › recovers when localStorage has a stale session API key 5.4s
mock-llm-auth-modes.spec.ts › auth mode: public gate › shows the auth screen when no key is configured 1.2s
mock-llm-auth-modes.spec.ts › auth mode: public gate › rejects an incorrect key with an inline error 1.3s
mock-llm-auth-modes.spec.ts › auth mode: public gate › allows access after pasting the correct key 1.7s
mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 728ms
mock-llm-auth-modes.spec.ts › auth mode: public gate › re-prompts when the server rotates its key (stale localStorage) 1.5s
mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 1: setup LLM profile and register automation trajectory 7.4s
mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 36.1s
⏭️ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 0ms
mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 6.4s
mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 6.2s
mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 36.1s
⏭️ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 0ms
mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance 16.0s
mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them 19.7s
mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 1: ensure mock LLM profile is configured 188ms
mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 2: start conversation and attach workspace metadata 36.1s
⏭️ mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 3: git control bar shows workspace pill and git actions 0ms
⏭️ mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 4: files tab defaults to diff view for attached workspace 0ms
⏭️ mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 5: browser tab shows empty state 0ms
⏭️ mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 6: files tab defaults to file-tree view without attached workspace 0ms
mock-llm-folder-workspace.spec.ts › mock-LLM folder browser → workspace → conversation › step 1: browse to a folder, add it as a workspace, and launch a conversation with the correct working_dir 36.7s
mock-llm-image-upload.spec.ts › mock-LLM image upload › attaching an image embeds it as base64 in the LLM completion call 43.2s
mock-llm-mcp-github.spec.ts › MCP GitHub server install flow › step 1: GitHub card is visible on the MCP marketplace page 5.7s
mock-llm-mcp-github.spec.ts › MCP GitHub server install flow › step 2: clicking GitHub card opens the install modal with correct fields 5.8s
mock-llm-mcp-github.spec.ts › MCP GitHub server install flow › step 3: full install flow — fill PAT, submit, verify installed 12.9s
mock-llm-mcp-github.spec.ts › MCP GitHub server install flow › step 4: installed GitHub server can be deleted 5.8s
mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 1: configure LLM, create switch-target profile, register trajectory 13.1s
mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch 36.1s
mock-llm-onboarding-happy-path.spec.ts › onboarding happy path › completes the full onboarding flow and launches a conversation 33.6s
mock-llm-onboarding-regressions.spec.ts › onboarding recent regressions › keeps the modal open on backdrop click and Escape 1.4s
mock-llm-onboarding-regressions.spec.ts › onboarding recent regressions › defaults the LLM setup step to OpenAI GPT-5.5 1.6s
mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes 7.4s
mock-llm-partial-stack.spec.ts › partial stack: --backend-only › serves backend APIs but returns 503 for the frontend root 13.1s
mock-llm-partial-stack.spec.ts › partial stack: port conflict › fails with a clear error when the ingress port is occupied 108ms
mock-llm-partial-stack.spec.ts › partial stack: port conflict › starts successfully on a free port after a conflict 6.0s
mock-llm-preset-automation.spec.ts › preset automation → slash command conversation › automation card sends the correct slash command to a conversation 43.4s
⏭️ mock-llm-preset-automation.spec.ts › preset automation → slash command conversation › direct slash command from home page triggers skill activation 0ms
mock-llm-profile-management.spec.ts › active profile deletion + reconciliation › active profile is deletable and reconciliation activates another profile 8.8s
⏭️ mock-llm-profile-management.spec.ts › same-model profile identity › chat header shows the correct profile when two profiles share the same model 0ms
mock-llm-profile-management.spec.ts › litellm_proxy proxy base_url preservation › re-saving a litellm_proxy profile from Basic view preserves the proxy base_url 0ms
mock-llm-skills.spec.ts › skill loading: project, user, and deletion › project skill in workspace/.agents/skills/ triggers on matching keyword 0ms
mock-llm-skills.spec.ts › skill loading: project, user, and deletion › user skill in ~/.openhands/skills/ triggers on matching keyword 0ms
mock-llm-skills.spec.ts › skill loading: project, user, and deletion › deleting a user skill removes it from subsequent conversations 0ms
mock-llm-ui-regressions.spec.ts › UI regressions › scopes standalone styles to the agent-server-ui shell 0ms
mock-llm-ui-regressions.spec.ts › UI regressions › renders critic results on agent messages and finish actions 0ms
mock-llm-ui-regressions.spec.ts › UI regressions › loads older events when scrolling up 0ms
mock-llm-ui-regressions.spec.ts › UI regressions › selected workspace persists after navigating away and returning 0ms
mock-llm-ui-regressions.spec.ts › UI regressions › cleared sessionStorage yields empty workspace selection 0ms
🔍 Failure details (9)

❌ mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 3: start ACP conversation and verify agent reply

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/conversations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/conversations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/conversations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 2: start conversation and attach workspace metadata

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/conversations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ mock-llm-folder-workspace.spec.ts › mock-LLM folder browser → workspace → conversation › step 1: browse to a folder, add it as a workspace, and launch a conversation with the correct working_dir

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/conversations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ mock-llm-image-upload.spec.ts › mock-LLM image upload › attaching an image embeds it as base64 in the LLM completion call

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/conversations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/conversations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ mock-llm-onboarding-happy-path.spec.ts › onboarding happy path › completes the full onboarding flow and launches a conversation

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/conversations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ mock-llm-preset-automation.spec.ts › preset automation → slash command conversation › automation card sends the correct slash command to a conversation

Error: expect(received).toMatch(expected)

Expected pattern: /\/conversations\/.+/
Received string:  "/automations"

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

Posted by the Mock-LLM E2E workflow · results are deterministic (scripted LLM responses)

@github-actions

Copy link
Copy Markdown
Contributor

🛑 Mock-LLM Docker E2E Test Results

17/43 passed · 8 failed · 18 skipped · ⚠️ 11 not run (process killed at 43/54)

Commit: 77596e75 · Workflow run · Test artifacts

Status Test Duration
chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 1: configure ACP agent via Settings → Agent UI 13.9s
chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 2: reload and verify ACP settings are persisted in UI 5.5s
chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 3: start ACP conversation and verify agent reply 36.2s
⏭️ chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 4: resume ACP conversation from sidebar after navigating away 0ms
chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 1: configure ACP agent via Settings → Agent UI 15.1s
chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 2: reload and verify ACP settings are persisted in UI 5.7s
chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 3: start ACP conversation and verify agent reply 35.2s
⏭️ chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 4: resume ACP conversation from sidebar after navigating away 0ms
chromium › mock-llm-auth-modes.spec.ts › auth mode: fresh install with runtime-injected key › reaches the onboarding modal without pre-seeded localStorage 1.5s
chromium › mock-llm-auth-modes.spec.ts › auth mode: non-public key rotation › recovers when localStorage has a stale session API key 5.3s
chromium › mock-llm-auth-modes.spec.ts › auth mode: public gate › shows the auth screen when no key is configured 1.2s
chromium › mock-llm-auth-modes.spec.ts › auth mode: public gate › rejects an incorrect key with an inline error 1.3s
chromium › mock-llm-auth-modes.spec.ts › auth mode: public gate › allows access after pasting the correct key 1.4s
chromium › mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 752ms
chromium › mock-llm-auth-modes.spec.ts › auth mode: public gate › re-prompts when the server rotates its key (stale localStorage) 1.5s
chromium › mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 1: setup LLM profile and register automation trajectory 7.4s
chromium › mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 36.1s
⏭️ chromium › mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 0ms
chromium › mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 1: setup LLM profile and register automation trajectory 8.3s
chromium › mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 35.2s
⏭️ chromium › mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 0ms
chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 6.4s
chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 6.2s
chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 36.2s
⏭️ chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 0ms
⏱️ chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 60.4s
⏭️ chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 0ms
⏭️ chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 0ms
⏭️ chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 0ms
⏭️ chromium › mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance 189ms
⏭️ chromium › mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them 213ms
chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 1: ensure mock LLM profile is configured 214ms
chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 2: start conversation and attach workspace metadata 36.2s
⏭️ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 3: git control bar shows workspace pill and git actions 0ms
⏭️ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 4: files tab defaults to diff view for attached workspace 0ms
⏭️ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 5: browser tab shows empty state 0ms
⏭️ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 6: files tab defaults to file-tree view without attached workspace 0ms
chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 1: ensure mock LLM profile is configured 309ms
chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 2: start conversation and attach workspace metadata 35.4s
⏭️ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 3: git control bar shows workspace pill and git actions 0ms
⏭️ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 4: files tab defaults to diff view for attached workspace 0ms
⏭️ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 5: browser tab shows empty state 0ms
⏭️ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 6: files tab defaults to file-tree view without attached workspace 0ms
🔍 Failure details (8)

❌ chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 3: start ACP conversation and verify agent reply

Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoMatch�[2m(�[22m�[32mexpected�[39m�[2m)�[22m

Expected pattern: �[32m/\/conversations\/.+/�[39m
Received string:  �[31m"/conversations"�[39m

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ chromium › mock-llm-acp-agent.spec.ts › mock-LLM ACP agent conversation › step 3: start ACP conversation and verify agent reply

Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoMatch�[2m(�[22m�[32mexpected�[39m�[2m)�[22m

Expected pattern: �[32m/\/conversations\/.+/�[39m
Received string:  �[31m"/conversations"�[39m

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ chromium › mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI

Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoMatch�[2m(�[22m�[32mexpected�[39m�[2m)�[22m

Expected pattern: �[32m/\/conversations\/.+/�[39m
Received string:  �[31m"/conversations"�[39m

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ chromium › mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI

Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoMatch�[2m(�[22m�[32mexpected�[39m�[2m)�[22m

Expected pattern: �[32m/\/conversations\/.+/�[39m
Received string:  �[31m"/conversations"�[39m

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM

Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoMatch�[2m(�[22m�[32mexpected�[39m�[2m)�[22m

Expected pattern: �[32m/\/conversations\/.+/�[39m
Received string:  �[31m"/conversations"�[39m

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

⏱️ chromium › mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server

�[31mTest timeout of 60000ms exceeded.�[39m

Error: locator.click: Test timeout of 60000ms exceeded.
Call log:
�[2m  - waiting for getByTestId('save-profile-btn')�[22m
�[2m    - locator resolved to <button disabled type="button" aria-busy="false" data-testid="save-profile-btn" class="h-9 min-h-9 inline-flex w-fit cursor-pointer items-center justify-center gap-2 px-3 rounded-lg transition-[background-color,border-color,box-shadow,opacity] duration-75 motion-reduce:transition-none text-sm font-normal disabled:cursor-not-allowed disabled:opacity-30 bg-primary text-[var(--oh-color-base)] hover:opacity-80">Save</button>�[22m
�[2m  - attempting click action�[22m
�[2m    2 × waiting for element to be visible, enabled and stable�[22m
�[2m      - element is not enabled�[22m
�[2m    - retrying click action�[22m
�[2m    - waiting 20ms�[22m
�[2m    2 × waiting for element to be visible, enabled and stable�[22m
�[2m      - element is not enabled�[22m
�[2m    - retrying click action�[22m
�[2m      - waiting 100ms�[22m
�[2m    104 × waiting for element to be visible, enabled and stable�[22m
�[2m        - element is not enabled�[22m
�[2m      - retrying click action�[22m
�[2m        - waiting 500ms�[22m

❌ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 2: start conversation and attach workspace metadata

Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoMatch�[2m(�[22m�[32mexpected�[39m�[2m)�[22m

Expected pattern: �[32m/\/conversations\/.+/�[39m
Received string:  �[31m"/conversations"�[39m

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

❌ chromium › mock-llm-files-and-git.spec.ts › files tab, git control bar, and browser tab › step 2: start conversation and attach workspace metadata

Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoMatch�[2m(�[22m�[32mexpected�[39m�[2m)�[22m

Expected pattern: �[32m/\/conversations\/.+/�[39m
Received string:  �[31m"/conversations"�[39m

Call Log:
- Timeout 30000ms exceeded while waiting on the predicate

Posted by the Mock-LLM E2E workflow · results are deterministic (scripted LLM responses)

@github-actions

Copy link
Copy Markdown
Contributor

📸 Snapshot Test Report

Warning

Snapshot comparison step crashed (timeout, OOM, or runner error) — diff results below may be incomplete or absent.
Check the CI logs for the full error output (look for the "Run snapshot comparison" step).

❌ 26 snapshots differ from the main branch baselines. Add the update-snapshots label to acknowledge intentional changes.

Category Count
🔴 Changed 26
🆕 New 0
✅ Unchanged 48
Total 74

How to resolve:

  • Unintentional diffs — the baselines on main may have moved since this branch was created. Merge the latest main into this branch and re-run CI.
  • Intentional changes — add the update-snapshots label. CI will pass and the new screenshots become the baseline when this PR merges.
🔴 Changed snapshots (26)

automations — 3 snapshots

automations-list-active-inactive

Expected (main) Actual (PR) Diff
expected actual diff

automations-no-automations

Expected (main) Actual (PR) Diff
expected actual diff

automations-search-no-results

Expected (main) Actual (PR) Diff
expected actual diff

backends-extended — 6 snapshots

backend-add-blank-disabled

Expected (main) Actual (PR) Diff
expected actual diff

backend-add-cloud-with-key-enabled

Expected (main) Actual (PR) Diff
expected actual diff

backend-add-local-ready

Expected (main) Actual (PR) Diff
expected actual diff

backend-add-two-column-layout

Expected (main) Actual (PR) Diff
expected actual diff

backend-add-whitespace-host-disabled

Expected (main) Actual (PR) Diff
expected actual diff

backend-manage-two-listed

Expected (main) Actual (PR) Diff
expected actual diff

backends — 2 snapshots

backend-add-modal

Expected (main) Actual (PR) Diff
expected actual diff

backend-manage-modal

Expected (main) Actual (PR) Diff
expected actual diff

changes-tab

changes-empty

Expected (main) Actual (PR) Diff
expected actual diff

mcp-page — 4 snapshots

mcp-custom-server-2-url-filled

Expected (main) Actual (PR) Diff
expected actual diff

mcp-empty-installed

Expected (main) Actual (PR) Diff
expected actual diff

mcp-search-filtered

Expected (main) Actual (PR) Diff
expected actual diff

mcp-slack-install-2-modal

Expected (main) Actual (PR) Diff
expected actual diff

onboarding

onboarding-step-2-setup-llm

Expected (main) Actual (PR) Diff
expected actual diff

settings-page — 4 snapshots

analytics-consent-modal

Expected (main) Actual (PR) Diff
expected actual diff

home-screen

Expected (main) Actual (PR) Diff
expected actual diff

settings-app-page

Expected (main) Actual (PR) Diff
expected actual diff

settings-page

Expected (main) Actual (PR) Diff
expected actual diff

settings-verification

condenser-settings

Expected (main) Actual (PR) Diff
expected actual diff

skills-page — 4 snapshots

skills-empty

Expected (main) Actual (PR) Diff
expected actual diff

skills-loaded

Expected (main) Actual (PR) Diff
expected actual diff

skills-no-match

Expected (main) Actual (PR) Diff
expected actual diff

skills-search-filtered

Expected (main) Actual (PR) Diff
expected actual diff
✅ Unchanged snapshots (48)

archived-conversation

  • conversation-panel-with-archived-badges
  • conversation-view-archived
  • conversation-view-sandbox-error

automations

  • automations-delete-modal

backends-extended

  • backend-add-cloud-advanced-open
  • backend-add-cloud-no-key-disabled
  • backend-add-form-partially-filled
  • backend-add-invalid-url-disabled
  • backend-add-name-only-disabled
  • backend-after-switch
  • backend-cancel-nothing-saved
  • backend-dropdown-two-backends
  • backend-edit-prefilled
  • backend-manage-after-removal
  • backend-remove-cancelled
  • backend-remove-confirmation
  • backend-switch-overlay

backends

  • backend-selector-open

changes-tab

  • changes-deleted-file
  • changes-diff-viewer

collapsible-thinking

  • reasoning-content-collapsed
  • reasoning-content-expanded
  • think-action-collapsed
  • think-action-expanded

mcp-page

  • mcp-custom-server-1-editor-open
  • mcp-custom-server-3-all-filled
  • mcp-custom-server-4-installed
  • mcp-custom-server-editor
  • mcp-slack-install-1-marketplace
  • mcp-slack-install-3-filled
  • mcp-slack-install-4-installed

onboarding

  • onboarding-step-0-check-backend
  • onboarding-step-1-choose-agent
  • onboarding-step-3-say-hello

projects-workspace-browser

  • projects-workspace-browser

settings-page

  • add-backend-modal

settings-secrets

  • secrets-add-form-filled
  • secrets-add-form
  • secrets-after-save
  • secrets-delete-confirm
  • secrets-list

settings-verification

  • verification-settings-critic-enabled
  • verification-settings-off
  • verification-settings-on

sidebar

  • sidebar-collapsed
  • sidebar-conversation-panel
  • sidebar-filter-menu

skills-page

  • skills-type-filter

Generated by the Snapshot Tests workflow. This comment was created by an AI agent (OpenHands) on behalf of the repo maintainers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants