Skip to content
This repository was archived by the owner on Jul 27, 2026. It is now read-only.

feat: add posthog events for install, conversation creation, and automations - #1036

Merged
HeyItsChloe merged 14 commits into
mainfrom
APP-2060/add-canvas-tracking-events
Jun 3, 2026
Merged

HeyItsChloe merged 14 commits into
mainfrom
APP-2060/add-canvas-tracking-events

Conversation

@HeyItsChloe

@HeyItsChloe HeyItsChloe commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor
  • A human has tested these changes.

Why

The canvas app had no structured app-level PostHog tracking. Raw usePostHog() calls were scattered across components with string event names, no shared properties, and no unified consent gate. A gap also existed where user_consents_to_analytics === null (settings still loading) left PostHog in its default capturing-enabled state, so events could fire before the user had made a consent decision.

Summary

  • Implements Option C — dual-system tracking: telemetry.ts handles anonymous library-level telemetry (npm consumers, pre-consent); useTracking hook handles identified app-level analytics (requires user_consents_to_analytics === true)
  • Adds 6 typed tracking functions to useTracking and migrates all 5 raw usePostHog().capture() call sites to use them
  • Fixes consent race condition: useSyncPostHogConsent now treats null as opted-out (safe default while settings load), not as a pass-through

Issue Number

APP-2060

How to Test

  1. cp .env.sample .env and set VITE_POSTHOG_CLIENT_KEY to a PostHog project key
  2. npm run dev
  3. Accept analytics consent via the modal — confirm PostHog receives conversation_created when starting a new conversation
  4. Open a conversation and send a first message — confirm initial_query_submitted fires
  5. Send a follow-up — confirm user_message_sent fires
  6. Open Settings, save — confirm settings_saved fires
  7. Deny analytics consent — confirm no further events are sent
  8. Pre-consent gate: clear localStorage, reload — confirm canvas_install fires in the PostHog dashboard before the consent modal is interacted with, and that no app-level events appear

If VITE_POSTHOG_CLIENT_KEY is not set the app runs normally with no tracking — this is the default.

Video/Screenshots

Screenshot 2026-06-03 at 12 30 47 PM

Type

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

Notes

  • VITE_POSTHOG_CLIENT_KEY is not required — omitting it silently disables System 2. Document it in .env.sample.
  • The two consent systems remain independent. System 1 (telemetry.ts) uses localStorage; System 2 uses the backend user_consents_to_analytics setting. The consent modal now syncs setTelemetryConsent as a convenience when running the full app, but library consumers using only the npm package are unaffected.
  • build:lib sets VITE_APP_ENV=production at compile time so the npm-published bundle always uses the production PostHog key. App Docker builds use the staging key unless the release workflow sets VITE_APP_ENV=production.
  • Tracking architecture boundary and rules for adding new events are documented in AGENTS.md.

🐳 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.24.0-python
Automation openhands-automation==1.0.0a6
Commit 0c5629d57865ca518e57886b0ab8e258103b7ebc

Pull (multi-arch manifest)

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

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-canvas:sha-0c5629d-amd64
ghcr.io/openhands/agent-canvas:APP-2060-add-canvas-tracking-events-amd64
ghcr.io/openhands/agent-canvas:pr-1036-amd64
ghcr.io/openhands/agent-canvas:sha-0c5629d-arm64
ghcr.io/openhands/agent-canvas:APP-2060-add-canvas-tracking-events-arm64
ghcr.io/openhands/agent-canvas:pr-1036-arm64
ghcr.io/openhands/agent-canvas:sha-0c5629d
ghcr.io/openhands/agent-canvas:APP-2060-add-canvas-tracking-events
ghcr.io/openhands/agent-canvas:pr-1036

About Multi-Architecture Support

  • Each tag (e.g., sha-0c5629d) 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-0c5629d-amd64) are also available if needed

…n, and prebuilt automation

- canvas_install already fires pre-consent via telemetry.ts
- Add conversation_created to NPM library telemetry (trackEvent) in use-create-conversation.ts onSuccess
- Add prebuilt_automation_enabled event in recommended-automations-launcher.tsx when a catalog automation is successfully launched
- Add prebuilt_automation_enabled event in automations-list.tsx and automation-detail.tsx handleToggle when enabling (not disabling)
- All consent-gated events route through telemetry.ts to PostHog prod via POSTHOG_PROD_KEY baked in by build:lib

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

vercel Bot commented Jun 2, 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 3, 2026 8:31pm

Request Review

- Add trackPrebuiltAutomationEnabled to useTracking hook (typed, named,
  includes commonProperties like all other tracked events)
- Remove duplicate conversation_created trackEvent call in
  use-create-conversation (trackConversationCreated already covers it)
- Replace all trackEvent(telemetry.ts) calls in automation routes and
  recommended-automations-launcher with useTracking hook functions
- Add trackPrebuiltAutomationEnabled to useCallback dep array in launcher

Co-authored-by: openhands <openhands@all-hands.dev>
System 1 — telemetry.ts (anonymous, library-level):
- canvas_install and canvas_new_session remain in telemetry.ts
- trackEvent() stays as the public library API (lib/index.ts export)
- Rule: never import trackEvent in app routes/components

System 2 — useTracking hook (identified, app-level):
- Wire posthog_client_key from VITE_POSTHOG_CLIENT_KEY env var in
  OptionService.getConfig() so PostHogProvider mounts when key is set
- Document VITE_POSTHOG_CLIENT_KEY in .env.sample
- Add 6 typed tracking functions to useTracking:
    trackInitialQuerySubmitted, trackUserMessageSent,
    trackDownloadVsCodeButtonClicked, trackSettingsSaved,
    trackMcpConfigUpdated, trackDownloadTrajectoryButtonClicked
- All include commonProperties (current_url, user_email) automatically
- Migrate all 6 raw usePostHog()/posthog.capture() call sites:
    chat-interface.tsx, conversation-card.tsx, settings-form.tsx,
    use-save-settings.ts, use-download-conversation.ts
- Rule: never call posthog.capture() directly from components

Consent unification:
- AnalyticsConsentFormModal now calls setTelemetryConsent() after saving
  user_consents_to_analytics so both systems honour the same decision

Documentation:
- AGENTS.md: tracking architecture section with boundary rules,
  system ownership table, and instructions for adding new events

Co-authored-by: openhands <openhands@all-hands.dev>
PostHog defaults to capturing enabled on init. The previous hook only
called opt_out_capturing() when user_consents_to_analytics was explicitly
false, leaving a window where events fired while settings were loading
(null) or on first visit before the user had made a decision.

Changes:
- null and false both route to opt_out_capturing() — only an explicit
  true opts PostHog in
- Remove hasSyncedRef: the hook now reacts to every settings change,
  which correctly handles consent granted in another tab or after the
  initial load. handleCaptureConsent is idempotent so repeated calls
  are safe.
- Update comment to document the three-state consent model

Co-authored-by: openhands <openhands@all-hands.dev>
New test files:
- use-sync-posthog-consent.test.ts (7 tests)
  Covers the null/false/true consent states and reactive re-sync
  behaviour. Specifically tests the bug fix: null now opts out.

- use-tracking.test.ts (17 tests)
  One test per tracking function. Verifies correct event names,
  property mapping (snake_case / SCREAMING_SNAKE_CASE), and that
  commonProperties (current_url, user_email) are included.
  Also covers git_user_email fallback and null user_email.

- analytics-consent-form-modal.test.tsx (5 tests)
  Verifies setTelemetryConsent is called with 'granted'/'denied'
  matching the form checkbox state on submission.

- use-save-settings.test.ts (4 tests)
  Covers trackMcpConfigUpdated: fires on new config, skips on
  absent config, skips on same object reference, handles zero counts.

- use-download-conversation.test.ts (3 tests)
  Covers trackDownloadTrajectoryButtonClicked: called on download,
  called before the API request, still called even when API rejects.

Updated test files:
- option-service.test.ts
  Two new cases for posthog_client_key: returns key from env var
  when set, returns null when env var is absent.

- chat-interface.test.tsx
  Added vi.mock('#/hooks/use-tracking') to prevent test noise.
  New 'Tracking' describe block: verifies trackInitialQuerySubmitted
  fires on first message and trackUserMessageSent on follow-ups.

- conversation-card.test.tsx
  Added vi.mock('#/hooks/use-tracking') guard.

- settings-form.test.tsx
  Added vi.mock('#/hooks/use-tracking') guard.
  New test: verifies trackSettingsSaved is called with LLM_API_KEY_SET
  and SEARCH_API_KEY_SET on form submission.

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

Copy link
Copy Markdown
Contributor Author

Tracked events reference

Always fires — no consent required

Event System When Key properties
canvas_install telemetry.ts First page load ever (deduped by localStorage flag) platform, user_agent, referrer, url_origin, embedded

canvas_install temporarily force-enables capturing, fires once, then restores the opted-out state if consent has not yet been granted.


Fires only after telemetry consent granted (localStorage["openhands-telemetry-consent"] === "granted")

These are library-level events available to npm consumers via the public trackEvent / trackSessionStart exports.

Event When Key properties
canvas_new_session Each new browser session (deduped by sessionStorage) is_first_use
trackEvent(name, props) Caller-defined — public npm API caller-defined

Fires only after analytics consent granted (user_consents_to_analytics === true)

All events below go through useTracking → posthog.capture(). They are silently dropped if the user has denied or not yet set consent (null now opts PostHog out via useSyncPostHogConsent). Requires VITE_POSTHOG_CLIENT_KEY to be set.

All events include current_url and user_email (falls back to git_user_email, then null).

Event Trigger location Additional properties
conversation_created use-create-conversation.ts → mutation onSuccess has_repository
prebuilt_automation_enabled automation-detail.tsx, automations-list.tsx, recommended-automations-launcher.tsx automation_id, automation_name, automation_category
initial_query_submitted chat-interface.tsx — first message sent (empty conversation) entry_point, query_character_length, replay_json_size
user_message_sent chat-interface.tsx — follow-up messages session_message_count, current_message_length
settings_saved settings-form.tsx — form submitted LLM_MODEL, LLM_API_KEY_SET, SEARCH_API_KEY_SET, REMOTE_RUNTIME_RESOURCE_FACTOR
mcp_config_updated use-save-settings.ts — when MCP config object reference changes has_mcp_config: true, sse_servers_count, stdio_servers_count
download_trajectory_button_clicked use-download-conversation.ts —
download_via_vscode_button_clicked conversation-card.tsx —
login_button_clicked Auth flow provider
push_button_clicked Git control bar —
pull_button_clicked Git control bar —
create_pr_button_clicked Git control bar —
user_signup_completed Signup flow signup_timestamp

Error telemetry — posthog.captureException

trackError() in error-handler.ts passes through to posthog.captureException. No explicit consent check — PostHog's opt-in/out state applies. Call sites: conversation-websocket-context.tsx (agent errors, token limit errors). actions.ts passes posthog: undefined making it a no-op.

github-actions Bot added a commit that referenced this pull request Jun 2, 2026
use-tracking.test.ts: COMMON.current_url was hardcoded as
'http://localhost/' but jsdom in CI runs at 'http://localhost:3000/'.
Change COMMON from a module-level const to a let populated in beforeEach
via window.location.href so it always matches the environment.

chat-interface.test.tsx: trackUserMessageSent was never called (0 times)
because totalEvents is derived from uiEvents via useFilteredEvents, not
from the events array. The test was setting events but leaving uiEvents
empty, so totalEvents stayed 0 and the component took the
trackInitialQuerySubmitted branch instead. Seed uiEvents with a minimal
MessageEvent (has llm_message.role + content, which isMessageEvent and
shouldRenderAgentServerEvent both accept) so totalEvents = 1.

Co-authored-by: openhands <openhands@all-hands.dev>
?? null was split onto its own line but the full expression fits
within the 80-char printWidth (79 chars), so prettier requires them
joined on one line.

Co-authored-by: openhands <openhands@all-hands.dev>
analytics-consent-form-modal.test.tsx:
  Spread of unknown[] is not valid — TS requires a tuple type or rest
  param. Replace (...args: unknown[]) => mock(...args) with a typed
  single-arg forwarder: (consent: string) => mock(consent).

use-save-settings.test.ts:
  MCPConfig requires shttp_servers (added in a later migration).
  Add shttp_servers: [] to all three fixture objects (full config,
  sharedConfig reference-equality test, and zero-count test).

Co-authored-by: openhands <openhands@all-hands.dev>
vi.fn(() => Promise.resolve()) infers zero parameters, so calling
it with a string argument fails TS2554. Add _consent: string to
the implementation so the inferred mock signature matches the call.

Co-authored-by: openhands <openhands@all-hands.dev>
All tracking functions now require posthog to be initialized AND
user_consents_to_analytics === true before calling posthog.capture().

Previously, events were blocked only via PostHog's opt-out mechanism
(set by useSyncPostHogConsent), leaving three gaps:
  1. posthog undefined (no VITE_POSTHOG_CLIENT_KEY) - silent no-op
  2. Desync between useSyncPostHogConsent and useTracking can cause
     events to fire or be dropped depending on hook ordering
  3. No self-documenting enforcement of the documented requirement
     that System 2 requires user_consents_to_analytics === true

The new internal track() helper short-circuits when either condition
is unmet, so each tracking function is self-contained and the consent
contract is explicit in the code that enforces it.

Tests: beforeEach now includes user_consents_to_analytics: true; posthog
mock is made variable so the undefined case can be tested; new 'consent
gate' describe block covers all blocked cases (false, null, undefined
settings, no posthog).

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

@malhotra5 malhotra5 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Calls posthog.identify(userId, { email }) for cloud users who have
granted analytics consent. Required because PostHogProvider is
initialized with person_profiles='identified_only', which silently
drops all events until identify() has been called.

Identity lifecycle:
- consent === true + userId present → posthog.identify()
- consent === false (explicit denial) → posthog.reset()
- userId becomes null after identify (logout) → posthog.reset()
- consent === null / settings loading → no-op

userId sourced from useCloudCurrentUserId() (cloud mode only).
Local mode is skipped — no stable server-issued user ID available.

Co-authored-by: openhands <openhands@all-hands.dev>
@HeyItsChloe
HeyItsChloe marked this pull request as ready for review June 3, 2026 19:55
@malhotra5 malhotra5 added the e2e-tests Triggers mock-LLM E2E tests on PRs label Jun 3, 2026
github-actions Bot added a commit that referenced this pull request Jun 3, 2026
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

❌ Mock-LLM Docker E2E Test Results

16/22 passed · 2 failed · 4 skipped

Commit: 1b69ea68 · Workflow run · Test artifacts

Status Test Duration
✅ mock-llm-auth-modes.spec.ts › auth mode: fresh install with runtime-injected key › reaches the onboarding modal without pre-seeded localStorage 1.4s
✅ mock-llm-auth-modes.spec.ts › auth mode: non-public key rotation › recovers when localStorage has a stale session API key 3.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.4s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › allows access after pasting the correct key 1.8s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 777ms
✅ 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 212ms
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 28.4s
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 4.3s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 3.7s
❌ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance (1 retries) 384ms
⏭️ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them (1 retries) 0ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 1: configure LLM, create switch-target profile, register trajectory 263ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch 4.8s
❌ mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes (1 retries) 413ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: --backend-only › serves backend APIs but returns 503 for the frontend root (1 retries) 0ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: port conflict › fails with a clear error when the ingress port is occupied (1 retries) 0ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: port conflict › starts successfully on a free port after a conflict (1 retries) 0ms
🔍 Failure details (2)

❌ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance

Error: build/index.html must exist — run `npm run build:app` first

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

❌ mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes

Error: build/index.html must exist — run `npm run build:app` first

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

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

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

✅ Mock-LLM E2E Tests

22/22 passed

Commit: 1b69ea68 · Workflow run · Test artifacts

Status Test Duration
✅ 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 3.6s
✅ 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.4s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › allows access after pasting the correct key 1.8s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 767ms
✅ 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 222ms
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 30.5s
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 4.8s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 4.6s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 4.5s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 4.4s
✅ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance 15.7s
✅ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them 19.4s
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 1: configure LLM, create switch-target profile, register trajectory 186ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch 4.9s
✅ mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes 7.3s
✅ 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 51ms
✅ mock-llm-partial-stack.spec.ts › partial stack: port conflict › starts successfully on a free port after a conflict 6.0s

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

github-actions Bot added a commit that referenced this pull request Jun 3, 2026
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

✅ Mock-LLM E2E Tests

22/22 passed

Commit: 0c5629d5 · Workflow run · Test artifacts

Status Test Duration
✅ mock-llm-auth-modes.spec.ts › auth mode: fresh install with runtime-injected key › reaches the onboarding modal without pre-seeded localStorage 1.6s
✅ mock-llm-auth-modes.spec.ts › auth mode: non-public key rotation › recovers when localStorage has a stale session API key 3.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.4s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › allows access after pasting the correct key 1.8s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 759ms
✅ 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 167ms
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 32.5s
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 4.5s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 3.8s
✅ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance 15.7s
✅ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them 20.4s
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 1: configure LLM, create switch-target profile, register trajectory 165ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch 4.9s
✅ 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 14.1s
✅ mock-llm-partial-stack.spec.ts › partial stack: port conflict › fails with a clear error when the ingress port is occupied 57ms
✅ mock-llm-partial-stack.spec.ts › partial stack: port conflict › starts successfully on a free port after a conflict 6.0s

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

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

❌ Mock-LLM Docker E2E Test Results

16/22 passed · 2 failed · 4 skipped

Commit: 0c5629d5 · Workflow run · Test artifacts

Status Test Duration
✅ 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 3.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.5s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › allows access after pasting the correct key 1.8s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 808ms
✅ 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 215ms
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 34.5s
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 5.4s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 3.7s
❌ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance (1 retries) 418ms
⏭️ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them (1 retries) 0ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 1: configure LLM, create switch-target profile, register trajectory 287ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch 4.9s
❌ mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes (1 retries) 420ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: --backend-only › serves backend APIs but returns 503 for the frontend root (1 retries) 0ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: port conflict › fails with a clear error when the ingress port is occupied (1 retries) 0ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: port conflict › starts successfully on a free port after a conflict (1 retries) 0ms
🔍 Failure details (2)

❌ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance

Error: build/index.html must exist — run `npm run build:app` first

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

❌ mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes

Error: build/index.html must exist — run `npm run build:app` first

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

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

github-actions Bot added a commit that referenced this pull request Jun 3, 2026
@HeyItsChloe
HeyItsChloe merged commit d9ee232 into main Jun 3, 2026
16 of 17 checks passed
@HeyItsChloe
HeyItsChloe deleted the APP-2060/add-canvas-tracking-events branch June 3, 2026 20:44
@openhands-release-bot openhands-release-bot Bot added the released: v1.2.0 Shipped in v1.2.0 label Jul 9, 2026
@openhands-release-bot

Copy link
Copy Markdown
Contributor

🚀 Released in v1.2.0.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

❌ Mock-LLM Docker E2E Test Results

16/22 passed · 2 failed · 4 skipped

Commit: 0c5629d5 · Workflow run · Test artifacts

Status Test Duration
✅ mock-llm-auth-modes.spec.ts › auth mode: fresh install with runtime-injected key › reaches the onboarding modal without pre-seeded localStorage 1.4s
✅ mock-llm-auth-modes.spec.ts › auth mode: non-public key rotation › recovers when localStorage has a stale session API key 3.3s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › shows the auth screen when no key is configured 1.1s
✅ 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.6s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 663ms
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › re-prompts when the server rotates its key (stale localStorage) 1.4s
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 1: setup LLM profile and register automation trajectory 156ms
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 25.9s
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 4.0s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 4.0s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 4.0s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 4.3s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 3.6s
❌ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance (1 retries) 307ms
⏭️ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them (1 retries) 0ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 1: configure LLM, create switch-target profile, register trajectory 192ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch 4.7s
❌ mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes (1 retries) 313ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: --backend-only › serves backend APIs but returns 503 for the frontend root (1 retries) 0ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: port conflict › fails with a clear error when the ingress port is occupied (1 retries) 0ms
⏭️ mock-llm-partial-stack.spec.ts › partial stack: port conflict › starts successfully on a free port after a conflict (1 retries) 0ms
🔍 Failure details (2)

❌ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance

Error: build/index.html must exist — run `npm run build:app` first

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

❌ mock-llm-partial-stack.spec.ts › partial stack: --frontend-only › serves the frontend but returns 503 for backend routes

Error: build/index.html must exist — run `npm run build:app` first

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

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

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

✅ Mock-LLM E2E Tests

22/22 passed

Commit: 0c5629d5 · Workflow run · Test artifacts

Status Test Duration
✅ mock-llm-auth-modes.spec.ts › auth mode: fresh install with runtime-injected key › reaches the onboarding modal without pre-seeded localStorage 1.6s
✅ mock-llm-auth-modes.spec.ts › auth mode: non-public key rotation › recovers when localStorage has a stale session API key 3.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.4s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › allows access after pasting the correct key 1.5s
✅ mock-llm-auth-modes.spec.ts › auth mode: public gate › skips auth screen for returning user with valid stored key 742ms
✅ 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 180ms
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 2: create automation and dispatch run via the UI 32.4s
✅ mock-llm-automation.spec.ts › mock-LLM automation lifecycle › step 3: verify automation and run on the automations page 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 1: create an LLM profile pointing at the mock LLM server 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 2: activate the mock-llm profile and verify settings API 4.2s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 3: run a conversation with the mock LLM 4.5s
✅ mock-llm-conversation.spec.ts › mock-LLM agent-server conversation › step 4: resume conversation from sidebar after navigating away 4.4s
✅ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → backend-only › frontend-only connects to a separate backend-only instance 15.6s
✅ mock-llm-cross-connect.spec.ts › cross-connect: frontend-only → multiple backends › connects to two separate backends and switches between them 19.4s
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 1: configure LLM, create switch-target profile, register trajectory 212ms
✅ mock-llm-model-switch.spec.ts › mock-LLM /model slash command › step 2: start conversation, switch profile via /model, verify switch 4.9s
✅ 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 14.1s
✅ mock-llm-partial-stack.spec.ts › partial stack: port conflict › fails with a clear error when the ingress port is occupied 53ms
✅ mock-llm-partial-stack.spec.ts › partial stack: port conflict › starts successfully on a free port after a conflict 6.0s

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

@github-actions

github-actions Bot commented Jul 9, 2026

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).

✅ No baseline found on main — all 73 snapshots are new and will become the baseline once this PR merges.

Category Count
🔴 Changed 0
🆕 New 73
✅ Unchanged 0
Total 73
🆕 New snapshots (73)

These snapshots have no baseline on main and will become the new baseline once this PR merges.

archived-conversation — 3 snapshots

conversation-panel-with-archived-badges

new snapshot

conversation-view-archived

new snapshot

conversation-view-sandbox-error

new snapshot

automations — 4 snapshots

automations-delete-modal

new snapshot

automations-list-active-inactive

new snapshot

automations-no-automations

new snapshot

automations-search-no-results

new snapshot

backends-extended — 19 snapshots

backend-add-blank-disabled

new snapshot

backend-add-cloud-advanced-open

new snapshot

backend-add-cloud-no-key-disabled

new snapshot

backend-add-cloud-with-key-enabled

new snapshot

backend-add-form-partially-filled

new snapshot

backend-add-invalid-url-disabled

new snapshot

backend-add-local-ready

new snapshot

backend-add-name-only-disabled

new snapshot

backend-add-two-column-layout

new snapshot

backend-add-whitespace-host-disabled

new snapshot

backend-after-switch

new snapshot

backend-cancel-nothing-saved

new snapshot

backend-dropdown-two-backends

new snapshot

backend-edit-prefilled

new snapshot

backend-manage-after-removal

new snapshot

backend-manage-two-listed

new snapshot

backend-remove-cancelled

new snapshot

backend-remove-confirmation

new snapshot

backend-switch-overlay

new snapshot

backends — 3 snapshots

backend-add-modal

new snapshot

backend-manage-modal

new snapshot

backend-selector-open

new snapshot

changes-tab — 3 snapshots

changes-deleted-file

new snapshot

changes-diff-viewer

new snapshot

changes-empty

new snapshot

collapsible-thinking — 4 snapshots

reasoning-content-collapsed

new snapshot

reasoning-content-expanded

new snapshot

think-action-collapsed

new snapshot

think-action-expanded

new snapshot

mcp-page — 11 snapshots

mcp-custom-server-1-editor-open

new snapshot

mcp-custom-server-2-url-filled

new snapshot

mcp-custom-server-3-all-filled

new snapshot

mcp-custom-server-4-installed

new snapshot

mcp-custom-server-editor

new snapshot

mcp-empty-installed

new snapshot

mcp-search-filtered

new snapshot

mcp-slack-install-1-marketplace

new snapshot

mcp-slack-install-2-modal

new snapshot

mcp-slack-install-3-filled

new snapshot

mcp-slack-install-4-installed

new snapshot

onboarding — 4 snapshots

onboarding-step-0-choose-agent

new snapshot

onboarding-step-1-check-backend

new snapshot

onboarding-step-2-setup-llm

new snapshot

onboarding-step-3-say-hello

new snapshot

projects-workspace-browser

projects-workspace-browser

new snapshot

settings-page — 5 snapshots

add-backend-modal

new snapshot

analytics-consent-modal

new snapshot

home-screen

new snapshot

settings-app-page

new snapshot

settings-page

new snapshot

settings-secrets — 5 snapshots

secrets-add-form-filled

new snapshot

secrets-add-form

new snapshot

secrets-after-save

new snapshot

secrets-delete-confirm

new snapshot

secrets-list

new snapshot

settings-verification — 3 snapshots

condenser-settings

new snapshot

verification-settings-off

new snapshot

verification-settings-on

new snapshot

sidebar — 3 snapshots

sidebar-collapsed

new snapshot

sidebar-conversation-panel

new snapshot

sidebar-filter-menu

new snapshot

skills-page — 5 snapshots

skills-empty

new snapshot

skills-loaded

new snapshot

skills-no-match

new snapshot

skills-search-filtered

new snapshot

skills-type-filter

new snapshot


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

This branch was successfully deployed

1 active deployment
Preview — 0c5629d5 Deployed Jun 3, 2026 by vercel[bot]
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

e2e-tests Triggers mock-LLM E2E tests on PRs released: v1.2.0 Shipped in v1.2.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants