Repository navigation
test(e2e): port view-state suite with reload and rehydration coverage (vps2 F6) - #1565
easonLiangWorldedtech wants to merge 439 commits into
Conversation
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one): 1
Please check the settings in the CodeRabbit UI or the
|
| Layer / File(s) | Summary |
|---|---|
View-state contract and webview identity packages/types/src/global-settings.ts, packages/types/src/vscode-extension-host.ts, webview-ui/src/utils/vscode.ts, webview-ui/src/context/ExtensionStateContext.tsx |
Adds a persisted view-state schema and sends a view-state ID at webview launch. The browser wrapper validates and persists IDs and uses in-memory state when browser storage fails. |
Durable state and settings portability src/core/config/ContextProxy.ts, src/core/config/importExport.ts, src/core/config/ProviderSettingsManager.ts, src/core/config/__tests__/*, packages/types/src/global-settings.ts |
Migrates two legacy provider keys to secret storage, excludes view-local state from settings import and export, and adds cross-instance profile-manager operations. |
View-local profile and mode mutations src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts, src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts, src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts |
Adds coverage for persisted view state, profile changes, mode-switch commit and rollback behavior, multi-instance isolation, and temporary view-ID ownership. |
Configuration reads and writes src/extension/api.ts, src/extension/__tests__/api-configuration.spec.ts, packages/types/src/api.ts |
Flattens configuration reads while excluding view-state and credential-bearing fields. Configuration writes save profiles and mappings with rollback handling. |
Launch validation and profile operations src/core/webview/webviewMessageHandler.ts, src/core/webview/__tests__/webviewMessageHandler.spec.ts |
Registers view IDs at launch and repairs invalid view profile pins. Profile, custom-mode, and sign-out updates use provider-level operations; profile renames include compensation paths. |
Task mode and extension API
| Layer / File(s) | Summary |
|---|---|
Task-scoped mode switching src/core/task/Task.ts, src/core/tools/SwitchModeTool.ts, src/core/task/__tests__/Task.spec.ts, src/core/tools/__tests__/switchModeTool.spec.ts |
Task submissions wait for mode initialization and delegate switches to the provider. The switch tool passes its task and reports a missing provider as a tool failure. |
Task creation and test-only API src/extension/api.ts, packages/types/src/api.ts, src/extension/__tests__/api-task-control.spec.ts, apps/vscode-e2e/src/suite/index.ts, apps/vscode-e2e/src/types/global.d.ts |
Adds task ask and follow-up controls, validated global-state accessors, optional editor preservation, and view-readiness handling for task creation. |
View-isolation E2E coverage apps/vscode-e2e/fixtures/modes.json, apps/vscode-e2e/src/fixtures/view-state.ts, apps/vscode-e2e/src/runTest.ts, apps/vscode-e2e/src/suite/view-state.test.ts |
Adds fixtures and E2E tests for mode isolation across views, multi-panel follow-up sequences, persisted state, and sidebar reload behavior. |
Sidebar and editor-tab command routing
| Layer / File(s) | Summary |
|---|---|
Tab command contributions and routing packages/types/src/vscode.ts, src/package.json, src/activate/registerCommands.ts |
Adds tab-specific commands and routes sidebar and tab actions to their respective providers. |
Tab creation and panel lifecycle src/activate/registerCommands.ts, src/activate/__tests__/registerCommands.spec.ts |
Tracks live tab panels, reuses available providers, serializes overlapping creation, and cleans up failed initialization. |
Task creation with fresh tab panels src/extension/api.ts, src/extension/__tests__/api-start-new-task.spec.ts |
Routes new-tab task creation through createClineTabPanel. |
Test and repository maintenance
| Layer / File(s) | Summary |
|---|---|
Test synchronization and repository settings webview-ui/src/i18n/__tests__/TranslationContext.locale-loading.spec.tsx, src/eslint-suppressions.json, .gitignore |
Locale tests await language-loading promises. Lint-suppression counts and ignore rules are updated. |
Estimated code review effort: 4 (Complex) | ~60 minutes
Change: Other
Sequence Diagram(s)
sequenceDiagram
participant VSCodeAPIWrapper
participant ExtensionStateContext
participant webviewMessageHandler
participant ClineProvider
ExtensionStateContext->>VSCodeAPIWrapper: Get view-state ID
ExtensionStateContext->>webviewMessageHandler: Send launch message with viewStateId
webviewMessageHandler->>ClineProvider: Register view-state ID
ClineProvider->>ClineProvider: Persist view-local state
Possibly related issues
- Zoo-Code-Org/Zoo-Code#2314 — The PR changes persisted view-state handling, but does not add the issue’s cross-host coordination.
- Zoo-Code-Org/Zoo-Code#2152 — The PR filters credential-bearing configuration and adds view-state registration and isolation tests, which overlap with the issue’s secret-exposure and loading objectives.
- Zoo-Code-Org/Zoo-Code#2151 — The PR changes view-ID validation and persisted-state handling; the issue’s proposed claim-token mechanism is not shown in the supplied change summary.
- Zoo-Code-Org/Zoo-Code#2303 — The PR adds per-view mode and profile handling and tests for isolation across panels.
Possibly related PRs
- Zoo-Code-Org/Zoo-Code#1567 — Both PRs change the same view-state E2E fixtures and launch-message path.
- Zoo-Code-Org/Zoo-Code#1560 — Both PRs change task-scoped mode switching,
SwitchModeTool, and related view-state and profile contracts. - Zoo-Code-Org/Zoo-Code#1562 — Both PRs change settings import, reset broadcasting, and the mode-switch contract.
Security Architecture Review
-
Security architecture risk: 🟡 Moderate · up to
b2333Per-view isolation improves command targeting, but profile identity and effective credentials can diverge after a lookup failure or overlapping profile switches. A subsequent task could use a different provider or account than the selected profile indicates. The demonstrated scope is within one extension host and its configured profiles; broader privilege escalation is not established.
Retained concerns
- Medium · security · inferred: The new rehydration path installs the persisted profile name before resolving its credential-bearing settings. If resolution fails, the name remains locally authoritative while getState falls back to shared provider settings. Subsequent task creation can therefore consume another profile's configuration under the pinned profile identity. Launch repair checks profile existence, so it does not necessarily repair a transient settings-resolution failure for an existing profile. This introduces a profile-ownership failure absent from the base's shared-only state model.
- Medium · security · inferred: Profile activation is queued per provider instance, although it snapshots sibling overlays and replaces shared credential settings. Two activations can both finish their snapshots before either shared write, then each clear its own overlay. The final shared configuration can consequently belong to profile Q while another view remains durably pinned to profile P. Sibling refresh selects only matching profile names and does not repair that mismatch. Existing tasks receive resolved activation settings directly, but later tasks consume the potentially mispaired view state.
Security review details
-
Security Blast Radius
- inferred — The demonstrated exposure involves sibling views, shared settings, and configured profiles within one extension host. Mispaired configuration can affect subsequent tasks' provider or credential selection. Cross-tenant consequences depend on the configured profiles; exposure to other installations or environments is not established.
Security Findings and Attack Paths
- inferred — A failed lookup or overlapping profile activation can leave a view displaying or persisting one profile while supplying another profile's settings to a new task. Sensitive task content could then be processed using the unintended provider or account. The inconsistent-state paths are source-supported; an executed exploit or unauthorized remote entrypoint was not demonstrated.
Trust Boundaries and Controls
- observed — Stable identity is supplied by the renderer and normalized by the extension, without an ownership proof in setViewStateId. This is a new state-lookup mechanism, but arbitrary profile activation was already available through renderer messages at base. Identity lookup alone therefore does not establish newly expanded credential authority.
- observed — Mode switching still validates the target mode and requests approval, now passes the originating task explicitly, and reports failure when its provider is unavailable. New-task creation retains organization allow-list validation of the effective configuration.
Resilience and Maintainability Implications
- observed — Successful settings import awaits cross-view invalidation before posting the initiating view's state. Broadcast failures are logged without failing the import, so completed import does not itself prove every live view was invalidated.
Hardening Proposals
- proposed — Treat profile identity and resolved configuration as one view-owned snapshot. Publish them together only after successful resolution, and serialize cross-view activation as a whole transaction or retain independent configuration snapshots for each pinned view. Failed recovery should expose an explicit unavailable-profile state rather than silently substitute shared credentials.
Caution
Pre-merge checks failed
Please resolve all errors before merging. Addressing warnings is optional.
- Ignore (reviewers only)

Passed checks: 4 passed
-
Full details: Title check
-
Explanation
The title names the primary change: a new e2e test suite for view-state with reload and rehydration coverage. The changeset includes that e2e port (modes.json fixture, view-state fixtures, runTest wiring, and view-state.test.ts with the sidebar reload test). The title omits the larger production work (fresh-tab creation, view-state persistence, profile and mode-switch handling, API surface), so it is partially related in scope. It is specific and readable, and it contains no file lists or vague terms. The 'vps2 F6' tag is internal series jargon, which a teammate scanning history may not recognize.
Full details: Description check
-
Explanation
The description covers most template sections. It has a Description section that explains the unit, a Test Procedure section with concrete commands, and a Pre-Submission Checklist. It states that the e2e suite was compile-checked but not run. Gaps: the 'Related GitHub Issue' section does not contain a 'Closes: #' line. It links only to a fork issue (41) and an upstream issue (
#1564) in prose, and the template requires every PR to link to an approved issue. The description is also very long, with many follow-up rounds, and it does not use the template's Simplified Technical English style in several places (for example, long sentences and a dense list of symbols). Several checklist items are unticked, and the Documentation Updates section has no selection. The description is mostly complete, so it passes.
-
Full details: Security Boundaries
-
Explanation
A failed profile lookup can send private task content to the wrong provider. In
src/core/webview/ClineProvider.ts:1177-1189,loadViewStatekeeps the pinned profile name whengetProfilefails, but does not load its settings.getStatethen uses shared provider settings at lines 5373-5379, andcreateTaskpasses them to the new task at lines 6523-6554. A transient secret-storage failure can therefore route a task intended for a private provider to another provider. The launch repair insrc/core/webview/webviewMessageHandler.ts:658-662does not repair this case if the later existence check succeeds.Resolution
Fail closed when a pinned profile cannot be resolved. Do not start tasks with shared provider settings until the pinned profile is loaded or the user explicitly selects a replacement. Apply the same rule to the reapplied-profile lookup, and add regression tests for transient lookup failures.
Full details: Persistence Integrity
-
Explanation
The prior failure remains. In
src/core/webview/ClineProvider.ts:3494-3555, the profile save completes before the newly added sibling snapshot. If a sibling profile read fails, the snapshot throws and the saved profile is not restored. At lines 3558-3576, an activation write can also fail aftersetValuehas persisted the new view pin; the successful writes are not compensated.upsertProviderProfilethen reports failure. The read-back compensation only covers failure at lines 3509-3535.Resolution
Make profile save and activation atomic, or compensate completed writes without overwriting concurrent changes. Report compensation failures. Add regression tests for sibling snapshot and activation-write failures.
Full details: Regression Evidence
-
Explanation
Fresh-task inheritance of a restored view-local mode lacks focused coverage.
ClineProvider.getState()now prefers view-local state, andTask.initializeTaskMode()reads that state. Provider tests mockTask; task tests do not restore a view-local mode. The reload test explicitly skips fresh-task inheritance, and the isolation test starts tasks with an explicit mode.Resolution
Add a focused test using a real provider and task. Restore a non-default view-local mode that differs from the shared mode, create a fresh task without an explicit mode, and assert the inherited mode. Dispose both objects.
Full details: Lifecycle Resource Cleanup
-
Explanation
View-state registration can continue after disposal. In
ClineProvider.ts:957-979, a blocked re-key can write after disposal. Registration then startsloadViewStateat line 1107, and a pending profile lookup can update the disposed provider at lines 1181-1192 and 1292. The changed launch handler continues after registration and can repeat custom-mode and profile synchronization for a replacement view (`webviewMessageHandler.ts:603-612, 631-705). Disposal does not invalidate this work.Resolution
Invalidate registration and load work when disposal starts. Check disposal before queued writes and after awaited lookups, while preserving consistency for writes already started. Stop launch handling and profile synchronization after disposal. Add regression tests that block the re-key queue and profile lookup, dispose the provider, and verify that no new work starts after release.
Draft PR — vps2 unit F6 (e2e view-state suite + fresh-tab API surface).
Tracking: fork issue 41 (vps2 series ledger). Upstream issue: #1564 (this series' gap record; the review lineage is #929, A5 re-review minor 2). Port source: the CS worktree apps/vscode-e2e (e9a44b2, the A5 re-review state) — 3 files byte-identical to CS; the reload test is F6-authored.
What this unit does at head 56264d1
Budget
Unit delta vs the stack base stays within budget (the e2e port is 4 files, +543/-0; the review-fix layer is production-hardening carried from the pre-merge review). The PR diff vs upstream/main is larger because the branch is stacked on the unmerged F1-F5 series; the series merges bottom-up, at which point each PR's CI gate measures its own unit. The CI mutation-diff changed-line advisory is a by-construction artifact of the stacked diff.
How to test
pnpm --dir apps/vscode-e2e test(mocked e2e) — the "Roo Code View State" suite incl. the sidebar reload/rehydration test.vitest run src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts— pruning, retention, profile-mutation and multi-instance describes.vitest run src/activate/__tests__/registerCommands.spec.ts— fresh-tab creation, tracked-tab reuse, init-failure cleanup, panel title test.vitest run src/extension/__tests__/api-task-control.spec.ts— startNewTask fresh-tab wiring without evicting a tracked tab's task.Verification numbers (head 56264d1)
Updated this round (b2333b1 -> d5a5895)
Mutation gate (carried)
Series mechanics
Pre-merge ERROR-row pre-check (head 599844c)
setViewStateIdnow rejects non-string ids (crafted postMessage), bounds the key length (MAX_VIEW_STATE_ID_LENGTH), and refuses ids already owned by another live instance; a refused view stays on its temporary id against shared state. Regression tests: non-string, oversized, claimed-by-another-view. Negative controls: disabling the ownership check reddens exactly the claim test; widening the length bound reddens exactly the oversized test; restores byte-exact (hash match).viewStateReadiness, so API-driven task creation never falls through to its timeout when a registration is refused._saveViewLocalStateFromMutationpersists the non-secret subset before updating the buffer; the re-key is a single queued write (no delete-then-write window; authorship clears only after the write lands); the mutation hook keeps the buffer synced across setValue/setValues and profile upsert/activation/deletion.--max-warnings=0clean; tsc clean except the two known local telemetry-dist noise lines; prettier merge-ref gate ALL-MATCHED.upsertProviderProfilewithactivate=false, and the zoo-gateway callback's non-active path) rewrote the profile on disk but left pinned views serving the pre-save snapshot; the non-activating branch now refreshes pinned sibling views (and this view when it holds the pin), and the callback routes throughupsertProviderProfileso the refresh cannot be forgotten.getState()no longer spreads the pinned overlay over the shared provider settings (a key the profile never defined leaked in from the shared profile: model id, region, tuning). The overlay is a complete snapshot: used in place, with onlyapiProviderfalling back to shared state.--max-warnings=0clean, tsc clean except the two known local telemetry-dist noise lines, prettier merge-ref gate ALL-MATCHED.Follow-up: overlay read from the view buffer (required-lane fix)
The previous round's overlay-completeness change read the overlay from the merged state values, which also carry the raw
apiConfigurationstate key. That key is a partial patch written by shared-store paths, not a view snapshot: with no overlay present and the raw key partial,getState()served only the partial object and dropped the shared provider settings the previous merge produced (the parallel-mode activation tests caught exactly this - the activated profile's model id vanished; required lane red on the units carrying that spec). The overlay is now read from the view buffer itself, where a present value is by contract a complete profile and an absent one means the shared store serves the settings.Proofs: ClineProvider.parallelMode.spec green where present (the six synchronization tests that regressed), ClineProvider.spec green including the overlay-completeness tests, sticky-mode and webviewMessageHandler green; negative control: reverting the overlay to a shallow merge over the shared settings still reddens exactly the leak test; restores byte-exact (hash match); eslint
--max-warnings=0clean; tsc clean except the two known local telemetry-dist noise lines; prettier merge-ref gate ALL-MATCHED.Follow-up: active task handlers rebuilt on non-activating saves (head eb27fac)
A review round on the view-identity unit found that the non-activating-save refresh updated pinned views' buffers but not their active tasks' API handlers, and the posted state prefers the task's own
apiConfiguration: a conversation running in a view pinned to the saved profile kept sending the old token (e.g. after the Zoo Gateway callback rotated it) even though the view's buffer had caught up. The refresh now rebuilds the affected tasks' handlers in the pinned views, and the non-activating branch rebuilds this view's own handler when it holds the pin.Proofs: ClineProvider.spec green (the pinned-sibling refresh test now asserts the active task's handler is rebuilt with the saved settings, not just the private buffer); negative control: dropping the rebuild from the refresh reddens exactly that assertion, restores byte-exact (hash match); parallelMode/sticky-mode/webviewMessageHandler green; eslint
--max-warnings=0clean; tsc clean except the two known local telemetry-dist noise lines; prettier merge-ref gate ALL-MATCHED.Follow-up: test-quality round from the review (head 320fd43)
The review round on the api-wiring unit named four test-quality issues; the ones present in this unit are fixed here: the
viewStatesdescribe block moved out of thealwaysDenyUnapprovedCommandsblock (test names carried the wrong subject), the prune test'stoBeDefined()survival checks replaced with exact-shapetoEqualassertions (they pass even if pruning strips fields), the stale-temporary-entry test's fixed 10 ms wait replaced with awaiting the load itself (the negative assertion could pass vacuously on a slow load), and the readiness-cancel test's fake timers restored in afinallyblock (a failed assertion left fake timers active and hung a later real-timer test).Proofs: global-settings 9/9 where present, ClineProvider.spec green, sticky-mode green where present, api-task-control green where present; negative controls: pruning that strips
updatedAtreddens exactly the prune test, aloadViewStatethat adopts the unowned temporary-id entry reddens exactly the stale-entry test, restores byte-exact (hash match); eslint--max-warnings=0clean (src and types); prettier merge-ref gate ALL-MATCHED.Follow-up: review-round fixes from the series (head b6341af)
A review round on this unit produced three fixes. The view-state readiness bound no longer rejects headless callers whose webview never launched (it only rejects when the webview has launched and readiness failed), and the IPC start-task listener catches rejections since the IPC server does not await async listeners (new headless-fallback and launched-stuck tests; negative control: an unconditional timeout reddens exactly the fallback test). The readiness-cancel test now pins the bound's own timer via fake timers and a zero timer count instead of a global
clearTimeoutspy, and the mode-ordering test asserts the switch has not been called before the pending initialization is released (negative controls: dropping the timer clear / the initialization await reddens exactly the respective assertion).Proofs: Task.spec 190/190, api-task-control 26/26; negative controls: dropping the initialization await reddens exactly the ordering test's in-flight assertion, dropping the readiness timer's clear reddens exactly the timer-count assertion, an unconditional timeout reddens exactly the headless-fallback test, restores byte-exact (hash match); eslint
--max-warnings=0clean; tsc clean except the two known local telemetry-dist noise lines; prettier merge-ref gate ALL-MATCHED.Updated this round (CodeRabbit follow-up, head e27816f)
handleModeSwitchUnlockednow captures the pre-switch task mode with a task-write rollback and reconciles every write it owns (shared mode, durable view pin, task mode, task history) when a failure lands after the task-mode write - including the mode-linked profile activation - sosubmitUserMessagedelivers on the pre-switch mode and profile instead of a half-applied switch; regression test included.taskApiConfigName); regression tests cover both sites.afterEachdisposes the per-test provider before resetting the instance registry; the webview state-payload test renamed.Updated this round (CodeRabbit follow-up 3, head ed668c4)
Updated this round (CodeRabbit follow-up 4, head 03913bf)
Updated this round (CodeRabbit follow-up 5, head 1759c8e)
Updated this round (CodeRabbit follow-up 6, head 9e20cc9)
Updated this round (CodeRabbit follow-up 7, head 786a1ce)
Updated this round (CodeRabbit follow-up 8, head 0e9337f)
Stryker suppression comments removed
Per the AGENTS.md policy (never add Stryker directives; remove them when a file is touched), all
// Stryker disabledirectives were dropped from this PR. Equivalence rationale for the removed suppressions:CR round 2026-10-15 (b)
setValuesmode validation (identical flagged block; regression test added) so the units stay byte-identical at merge.CR round 2026-10-15 (c)
getConfigurationkeeps the sharedapiProvideras a fallback base when a pinned view-local profile carries no provider (matchinggetState()); all other shared provider-setting keys stay stripped. Regression test added; the identical block in fix(api): wire task controls and sidebar-targeted configuration (vps2 F5) #1557/fix(webview): show modes skeleton until view state loads (vps2 F7) #1567 received the same fix.finallypath indispose()and the tab-init failure cleanup nests the dispose await so a cleanup rejection cannot replace the initialization error (same fix as fix(webview): send stable view-state id on launch and re-pin per-view state #1552; regression tests added).CR round 2026-10-15 (d)
apiProvider(identical flagged block; regression test added).CR round 2026-10-15 (e)
ClineProvider.dispose()now covers the whole disposal operation with the deregistrationfinally, so a throw in any cleanup step can no longer leave the provider registered while_disposedblocks a retry; regression test added. Same fix in fix(api): wire task controls and sidebar-targeted configuration (vps2 F5) #1557/fix(webview): show modes skeleton until view state loads (vps2 F7) #1567 (identicaldispose()body).createTaskrestores the previous mode selection when custom-mode registration rejects (identical flagged block; regression test added).CR round 2026-10-15 (f)
Row-9 update (head 44d7a56)
setGlobalStatepersistsparsed.datainstead of the raw payload: non-strict schemas strip unknown fields only in the parsed value, so credential-shaped extras cannot reach durable global state (CR Security Boundaries).ContextProxy#updateGlobalStatelayer on this unit; the new tests pin that guard.Verification: new tests are focused negative controls (reverting each guard reddens exactly the matching test); ClineProvider.spec, webviewMessageHandler.spec spot-checks, api specs, spec-inclusive tsc --noEmit, eslint --max-warnings=0 (suppression counts unchanged) and prettier all green on this head.
Upstream refresh (head 44d7a56)
Merged the current
upstream/main(merge commit; the row-9 fix commit is unchanged and is the first parent) to clear the review gate's mergeability hold. Upstream refactoredupsertProviderProfileinto a newpersistProviderProfilemethod and added arunToCompletionqueue option; this unit's transactional body was ported into the new method unchanged and upstream'sPromise.allSettledactivation fix was adopted in it. ClineProvider.spec, webviewMessageHandler.spec,tsc --noEmit, eslint (suppression counts unchanged) and prettier re-verified green at this head. The first refreshed head's windows unit-test lane caught that upstream's newupdateProfileModeltests inClineProvider.apiHandlerRebuild.spec.tsmock the settings manager with a staticgetProfile, which starves the post-save read-back; the mock now repointsgetProfileat whatsaveConfigwrote (the real manager's behavior), and the file's 38 tests pass locally alongside the rest of the verification set.