fix(vscode): use Node design-time worker for codeful projects + cross-platform codeful E2E - #9463
Merged
Brian Lam (lambrianmsft) merged 3 commits intoJul 28, 2026
Conversation
…-platform codeful E2E Codeful F5 debug fails with MSB3026: the design-time host holds a lock on lib/codeful/*.dll, so the csproj CopyToCodefulFolder target cannot overwrite them. Killing the two lingering func.exe processes unblocks debug. Root cause: Azure#9410 flipped the design-time worker from Node to dotnet + FUNCTIONS_INPROC_NET8_ENABLED so the Data Mapper Test panel could spawn the NetFxWorker. Under Node the design-time host never loaded the project's compiled assemblies; under in-proc .NET 8 it does, and it locks them. Azure#9377 (regenerate workflow-designtime for source-controlled projects) made the host reliably present on open, which is what exposed the latent ordering bug. pickFuncProcess only waits for the previous func *task* to stop, so the design-time worker survives the build. Fix: codeful projects pin the design-time host to the Node worker via shouldUseNodeDesignTimeWorker(). Node never loads lib/codeful, so nothing is locked and no per-debug kill/restart of the design-time host is needed. Applied in both regenerateDesignTimeDirectory (generate Node settings) and validateDesignTimeDirectory (an existing dotnet codeful design-time file is now treated as invalid and regenerated, so projects already broken by 5.98x self-heal on the next design-time start). Non-codeful behavior is unchanged. E2E: the codeful debug suite had no CI presence at all, which is why this shipped. It now runs on ubuntu-latest and windows-latest, asserting the deterministic cross-platform regression signal (design-time FUNCTIONS_WORKER_RUNTIME=node, no FUNCTIONS_INPROC_NET8_ENABLED) plus buildExit === 0. Supporting CI work: - setup-runtime-deps-windows seeds la-runtime-deps-Windows-v1, which previously could never be written: no Windows job both hydrated the deps and succeeded, and actions/cache is post-if: success(). Windows codeful therefore hydrated dependencies underneath the already-running test, which was the real cause of its buildStart:0 failures - not a hosted-runner func-exec gap. - Codeful jobs use an explicit cache restore/save pair gated on a deps-check that executes `func --version`, so a half-hydrated tree is never cached. A partial cache would restore as an exact hit forever and never self-repair. - The E2E dependency gate now requires `func --version` to exit 0 rather than just checking existence + execute bit, matching what the product's pre-debug gate actually spawns. Both codeful legs are ADVISORY (continue-on-error, excluded from the vscode-e2e-summary hard gate). Their deterministic worker=node assertion passes every run, but the F5 task-chain assertions layered on top still ride on an unfixed Azurite startup race (preDebugValidate -> validateEmulatorIsRunning pops a headless-unanswerable AzureWebJobsStorage modal that cancels the debug session). That fix is a separate change set; promoting both legs to blocking is the deliverable of the follow-up PR. Also adds unit coverage for the codeful connection view against empty/missing connections.json, following a report of an empty connections.json on 5.981.0. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5745044d-d3ac-4293-8c22-a66762e9170e
Contributor
🤖 AI PR Validation ReportPR Review ResultsThank you for your submission! Here's detailed feedback on your PR title and body compliance:✅ PR Title
✅ Commit Type
✅ Risk Level
✅ What & Why
✅ Impact of Change
✅ Test Plan
✅ Contributors
✅ Screenshots/Videos
Summary Table
All checks passed — this PR is compliant with the team template and ready to merge.Powered by: Copilot CLI (claude-opus-4.8) | Last updated: Tue, 28 Jul 2026 15:02:42 GMT |
14 tasks
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a codeful-only VS Code debug regression by forcing codeful projects’ design-time host to run with the Node worker (avoiding lib/codeful DLL locks that break F5 builds), and adds cross-platform CI/E2E coverage so the codeful debug path is exercised on both Ubuntu and Windows runners.
Changes:
- Add
shouldUseNodeDesignTimeWorker(...)and wire it into design-time validation/regeneration so codeful projects self-heal toFUNCTIONS_WORKER_RUNTIME=node. - Strengthen the E2E harness to gate on “
func --versionexits 0” (not just file existence/execute bit), and expand the codeful debug E2E to be cross-platform. - Extend the
vscode-e2eGitHub Actions workflow with new Windows and codeful debug jobs (advisory) plus Windows runtime-deps seeding.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| apps/vs-code-designer/src/app/utils/codeless/validateProjectArtifacts.ts | Introduces shouldUseNodeDesignTimeWorker and applies it to design-time validation/regeneration to force codeful projects onto Node worker. |
| apps/vs-code-designer/src/app/utils/codeless/test/validateProjectArtifacts.test.ts | Adds/updates unit tests ensuring codeful design-time settings validate/regenerate to Node worker even when the workspace setting is off. |
| apps/vs-code-designer/src/test/ui/runtimeBinaryCheck.ts | Adds funcVersionRuns() to verify the Functions Core Tools binary is actually runnable via func --version. |
| apps/vs-code-designer/src/test/ui/designerHelpers.ts | Updates dependency validation polling to require a runnable func, aligning the E2E gate with the product’s pre-debug behavior. |
| apps/vs-code-designer/src/test/ui/run-e2e.ts | Adjusts codeful debug phase orchestration (freshness watermark env var) and patches generated code to keep debug-guard builds compiling. |
| apps/vs-code-designer/src/test/ui/codefulDebugTasks.test.ts | Enhances codeful debug E2E to open the workspace deterministically, assert Node design-time worker, and wait for runtime deps before F5. |
| apps/vs-code-designer/src/app/commands/workflows/connectionView/panels/test/connectionPanel.test.ts | Adds regression coverage for empty/missing connections.json shapes in connection view logic. |
| .github/workflows/vscode-e2e.yml | Adds Windows “beachhead” job, advisory codeful debug jobs for Ubuntu/Windows, and a Windows runtime-deps cache seeding job. |
Address review feedback on the generated codeful workflow fixture. The patched GetWorkflow() invoked trigger.Then(...) as a bare statement and passed `trigger` to CreateStatefulWorkflow, which is only correct if Then() mutates the trigger in place. The shipped assets/CodefulProjectTemplate/StatefulCodefulWorkflow template captures the chained result (`var workflow = trigger.Then(...)`) and passes that, so the fixture now does the same. The statement form was an unnecessary part of the earlier CS0266 fix. That error was purely the return type - the method was declared IWorkflowTrigger while WorkflowFactory.CreateStatefulWorkflow returns FlowDefinition - and changing the return type alone resolves it. No behavior change is expected: the class implements no interface and services.AddWorkflowProviders is stripped from Program.cs, so GetWorkflow() is never invoked. The fixture exists only so the generated codeful project compiles and exercises the debug task chain. This keeps it representative of what the product template actually generates and removes the dependency on unspecified Then() side effects. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5745044d-d3ac-4293-8c22-a66762e9170e
Andrew Eldridge (andrew-eldridge)
approved these changes
Jul 28, 2026
14 tasks
Brian Lam (lambrianmsft)
added a commit
that referenced
this pull request
Jul 29, 2026
…lidation (#9470) * fix(vs-code-designer): wait for Azurite readiness before pre-debug validation Codeful and codeless F5 could hang indefinitely when Azurite had not finished starting. `activateAzurite` issued the Azurite start command and returned immediately, so `preDebugValidate` -> `validateEmulatorIsRunning` often ran while the emulator was still binding its ports. The blob probe then threw and the catch branch awaited a `modal: true` warning ("Failed to verify AzureWebJobsStorage connection ... Debug anyway"). Nothing can answer a modal in a headless or automated session, so the await never resolved and `vscode.debug.startDebugging()` itself never settled. The observed CI signature was a lone `debugStart` recorder event with neither `debugStarted` nor `debugStartFailed`, `events captured=1`, `buildStart:0`, and a 12-minute wait ending in the misleading "F5 never reached the codeful task chain". Byte-identical code produced both green and red runs purely on this race. Product changes: - `activateAzurite` now awaits `waitForAzuriteReady()` after issuing the start command. It polls the NON-prompting `validateEmulatorIsRunning` overload (10 x 500 ms) and throws a bounded, actionable error instead of ever reaching a modal. Because `activateAzurite` runs before `preDebugValidate` in `pickFuncProcess`, the hanging modal branch is now unreachable on the auto-start path. - `validateEmulatorIsRunning` accepts `ValidateEmulatorOptions` (still accepting the previous boolean for compatibility). `preDebugValidate` passes `allowDebugAnyway: !autoStartAzurite`, so "Debug anyway" is no longer offered when the extension owns Azurite's lifecycle and has already reported a failure. - `preDebugValidate` distinguishes a missing `AzureWebJobsStorage` value from an unreachable emulator and reports the former directly. - `pickFuncProcess` and `startRuntimeApi` set `errorHandling.rethrow` and propagate Azurite failures so debug aborts with a clear message rather than silently continuing into validation. - `executeOnAzurite` forwards caller arguments verbatim. Spreading `args` into an object literal produced numeric keys ({0:'a',1:'b'}) and collapsed them into a single argument. It also fails with an actionable message when the Azurite extension is missing or cannot be activated. Test coverage: - 23 unit tests across five files covering the readiness polling, the timeout error, the options overload, `allowDebugAnyway` selection, argument forwarding, and the rethrow paths in `pickFuncProcess` / `startRuntimeApi`. - New E2E Phase 4.13 (`E2E_MODE=azuriteonly`), wired into `run-e2e.ts` as a create/assert pair. 4.13A builds a workspace through the real Create Workspace webview; 4.13B reopens it in a fresh session, binds TCP 10000/10001/10002 so Azurite physically cannot start, presses F5, and asserts the bounded "Azurite did not become ready" error appears while the AzureWebJobsStorage modal and the "Debug anyway" affordance never do and the debug toolbar stays hidden. The failure is forced rather than raced, so the phase is deterministic. - `codefulDebugTasks` now fails fast when `debugStart` is followed by neither `debugStarted` nor `debugStartFailed` and no task activity at all. That combination can only mean `startDebugging` never returned. It reports the modal as the cause with a screenshot instead of burning the full budget on a misleading message. The threshold is anchored on the recorder's own bounded 360 s command-registration wait so healthy slow runs cannot trip it. CI: - Adds the `vscode-e2e-azurite (ubuntu-latest)` job, with a preflight that fails loudly if 10000/10001/10002 are already bound so a pre-existing emulator is reported as infrastructure rather than a product regression. - Promotes `vscode-e2e-codeful-ubuntu` and `vscode-e2e-codeful-windows` from advisory to BLOCKING. They were left `continue-on-error` in #9463 solely because their F5 assertions rode this race; that race is fixed here, so all three jobs join the `vscode-e2e-summary` hard gate. `setup-runtime-deps-windows` stays advisory by design: it is a cache seeder, and keeping it non-blocking is what guarantees the Windows codeful leg RUNS rather than being skipped. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5745044d-d3ac-4293-8c22-a66762e9170e * fix(vs-code-designer): repair Phase 4.13A warm-up and reduce debug-path file I/O Addresses the single red check on this PR plus all three Copilot review comments. ## Phase 4.13A failure (the only failing job) `vscode-e2e-azurite` failed in Phase 4.13A (workspace creation), so Phase 4.13B never ran and the Azurite assertion was never evaluated. The product fix was not implicated: the port preflight passed, Azurite installed and activated normally, and both codeful legs were green on ubuntu and windows. The E2E ran for only 9 seconds. Two different exceptions appeared across runs — `ElementNotInteractableError` from `InputBox.clear -> InputBox.setText`, and `TimeoutError` from `input.getQuickPicks()` — with one cause: the `azureLogicAppsStandard.createWorkspace` palette command was not registered yet. Failure screenshots show VS Code on the Welcome screen while the extension was still downloading the .NET SDK, Functions Core Tools, NodeJS and workflows bundle 1.165.52, with the bundle-progress and C# Dev Kit "Sign in" toasts on screen. `azuriteAutostartFailure.test.ts` originated in an earlier PR, was never wired into `run-e2e.ts`, and therefore had never executed in CI. It hand-rolled ~250 lines duplicating `createWorkspaceShared.ts`, and that copy predated two fixes the shared module already carries and documents: - shared `selectCreateWorkspaceCommand` uses `typeQuickInputQuery()` (raw sendKeys) precisely because "raw ExTester InputBox.setText()/clear() throws ElementNotInteractableError" on slow CI runners; - shared opens a FRESH palette for the fallback search because "reusing a no-pick widget can race VS Code clearing the palette and leave the input hidden in CI". The local fork did neither, and each omission produced one of the two observed failures. Changes: - Delete the duplicated automation and delegate to `createWorkspaceShared.ts` (`selectCreateWorkspaceCommand`, `switchToWebviewFrame`, `waitForCreateWorkspaceFormReady`, `fillStandardFormFields`, `clickCreateWorkspaceButton`), mirroring the green `createWorkspace.fixtures.test.ts` Standard + Stateful path step for step. This removes the drift permanently rather than hand-porting the fixes. - Add the three-stage warm-up gate the test previously lacked entirely: 1. `waitForExtensionReady` (hard gate — nothing works until the command exists), 2. `waitForExtensionValidationComplete` (best-effort, non-fatal by design: it throws on timeout and via `assertFuncCoreToolsExecutable`, and its third phase waits on the design-time API, which cannot start in 4.13A because no workspace is open yet — workspace creation is file scaffolding and does not need `func` on disk, so a slow validation must not redden a run creation would have survived), 3. `clearBlockingUI` for the bundle-download and C# Dev Kit toasts, which steal focus from the quick input and intercept webview clicks. - Use the Selenium Actions API for the Next button instead of a JS click, so React synthetic handlers actually fire. - Add numbered step screenshots, a `FAIL-*` screenshot, and a workspace directory listing on failure so the next red run is self-diagnosing. - Replace a bare 15 s sleep with detection-based polling for the generated `.code-workspace`, app directory and `local.settings.json`. - Raise the job timeout to 45 minutes, matching the codeful legs: the cold-runner warm-up plus 4.13B's 180 s design-time wait and F5 assertions did not fit in 30. Generated paths (`azuritews` / `azuriteapp` / `workflow1`), the module-level `AZURITE_E2E_STEP` gate, and the codeless Standard workspace type are unchanged, so `run-e2e.ts` still finds the workspace it hands to Phase 4.13B. ## Review comment: stale phase number (3667994827) True, and worse than reported: Phase 4.9 is a real, unrelated phase (`descriptionPersistence`), so the stale reference actively misdirected log correlation rather than merely naming something nonexistent. Corrected to 4.13A, and the assert file to 4.13B. ## Review comment: unawaited server.close() (3667994865) True. `blockAzuritePorts()` closed partially-bound servers without awaiting, while a `closeServers()` helper that bounds each close at 1 s already existed. The practical impact is smaller than described — `close()` releases the listening socket immediately and these servers hold no live connections — but there is a worse latent bug underneath: `portBlockers` is only assigned once `blockAzuritePorts()` resolves, so on the throw path `after()` cleans up an empty array and that unawaited loop was the only cleanup those sockets would ever get. Now awaits `closeServers(servers)`. ## Review comment: duplicated AzureWebJobsStorage reads (3667994902) True, and larger than reported. `getAzureWebJobsStorage()` calls `getLocalSettingsJson()`, which stats, reads and parses `local.settings.json` on every call with no memoization. The reviewer counted 2 reads on the pre-debug path; because `waitForAzuriteReady` polls up to 10 times, the auto-start path performed 4 typical and 13 worst-case. Now 1 and 2. `ValidateEmulatorOptions` gained an optional `azureWebJobsStorage`, and `activateAzurite` hoists a single read out of the polling loop. Only the settings read is hoisted — every readiness attempt still calls `validateEmulatorIsRunning` and still performs the blob probe, so emulator liveness is never cached. Two new unit tests lock that invariant in: one asserts the settings file is read exactly once while `validateEmulatorIsRunning` is called four times, the other asserts a supplied value skips the read while `createBlobService` still runs per call. "Not supplied" is distinguished from "explicitly undefined" via an `in` check rather than a nullish fallback, and the pre-existing boolean overload is unchanged. Unit tests: 25 passing across 5 files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5745044d-d3ac-4293-8c22-a66762e9170e * fix(vscode): address expert review findings on Azurite readiness A five-lane review board (product, unit, e2e, CI, goal-alignment) found defects in this PR, including two regressions the PR itself introduced. Fixes them and hardens the tests that failed to catch them. Product regressions introduced by this PR, now fixed: - validatePreDebug added a modal warning when AzureWebJobsStorage was missing, on the resolveDebugConfiguration path. That is the exact bug class this PR exists to remove: an awaited modal there never settles headlessly. Pre-PR a missing value fell through and debug proceeded. Now a non-modal warning plus an output-channel log, and it returns true to restore the original continue semantics. - activateAzurite began awaiting executeOnAzurite(azurite.start). The third-party extension rejects that command when the port is already bound, which is the normal state when a healthy Azurite is serving a second debug session, so debug would fail against a working emulator. Pre-PR the call was fire-and-forget. Now the rejection is logged and recorded in telemetry, and the readiness probe stays authoritative. Pre-existing hazards surfaced by the review: - The remaining !allowDebugAnyway branch still awaited a modal, and it is the DEFAULT path because allowDebugAnyway is !autoStartAzurite and auto-start defaults on. It now throws, which surfaces the same text through the non-modal command error notification. - doesContainerExist had no timeout, so the "bounded" readiness wait was only bounded in its sleeping. A listener that accepts and never responds reopened the hang. Added azuriteProbeTimeoutMs, and the timeout message now reports measured elapsed time instead of a retry-budget product that no longer describes the real bound. Test hardening (all mutation-verified): - pickFuncProcess/startRuntimeApi azurite tests imported and mocked verifyLocalConnectionKeys, which does not exist; the real symbol is refreshConnectionKeys. The guard assertions were permanently vacuous and a genuine ordering regression surfaced as a mock-resolution crash before the assertion ran. The package tsconfig excludes *.test.ts and vitest transforms without type-checking, so CI could never catch it. - The retry contract was entirely unpinned: 10->5 plus 500ms->0 plus a garbage message left every test green. Exported the constants and pinned both the relationship and the values; deriving expectations purely from the imports would move product and test together. - Tightened the double-toast assertion, which passed with suppressDisplay deleted, and added an off-by-one guard on the final allowed attempt. E2E fixes: - Phase 4.13B gated only on existsSync(workflow-designtime) with no freshness check, and 4.13A leaves that folder behind, so the gate could return instantly on a stale folder. Added an mtime watermark plus launcher-side cleanup before 4.13B, when the 4.13A process has already exited and deletion cannot hit the Windows EBUSY degrade path. 4.13B also had no extension-ready gate, the same exposure that killed 4.13A. - The codeful hang guard anchored on debugStart, which the recorder writes BEFORE extension registration, so the effective grace was 420 - R seconds and could fire 66 seconds into startDebugging on a healthy run. Both codeful legs are now blocking, so that could fail real builds. Added a debugInvoke recorder phase emitted immediately before startDebugging and anchored the guard on it at 600s. - Negative assertions could pass on an empty scrape; added a liveness requirement and fixed the cause by returning to the default frame before scraping. CI: - LA_E2E_SCENARIO_RETRIES was dead config for every newly-blocking job: it is read only inside runScenarioPhases, which the codeful and azurite modes never reach. Wired both through a fresh-session group retry. - Documented that continue-on-error forces needs.<job>.result to "success" regardless of conclusion, so it is only safe on jobs the summary gate does not check. 129 test files / 1584 tests pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5745044d-d3ac-4293-8c22-a66762e9170e * test(vscode): cover codeful + codeless Azurite readiness on Ubuntu and Windows Closes review finding G3. The Azurite readiness regression test added earlier in this PR only proved the fix on ONE of the four combinations that matter: codeless, on Ubuntu. The reported failure came from codeful on Windows. Why codeful needed its own coverage rather than being assumed equivalent: pickFuncProcessInternal calls activateAzurite unconditionally at :85 and preDebugValidate at :100, well before the isCodeless branch at :142. Codeful therefore traverses the exact readiness path this PR changes -- but it also reaches publishCodefulProject at :116 and the func-host build chain at :146, so a regression that let the wait fall through would burn a full dotnet build before failing. The codeful assertion pins that: no build output may exist after the bounded failure. Test changes - Phase 4.13A/4.13B are parameterized by app kind via AZURITE_E2E_APP_KIND. Unset resolves to codeless with byte-identical workspace paths, app names and launch config to the previous revision, so the existing scenario is unchanged. - The codeful kind uses a fully disjoint workspace parent, workspace and app name, so the two kinds cannot collide on disk or in the .code-workspace. - The codeful launch config mirrors what CreateLogicAppVSCodeContents actually emits (isCodeless: false, no preLaunchTask; pickFuncProcessInternal falls back to isFuncHostTask when it is absent) rather than inventing a shape. - Codeful-only: build output under lib/codeful and any <app>.dll are cleared immediately before F5, then asserted absent afterwards, so the "failed before publish" claim cannot pass vacuously. obj/ is logged but never asserted, since C# Dev Kit design-time evaluation writes it legitimately. - Both kinds assert the WORKFLOW_CODEFUL_ENABLED marker, so a green codeful run cannot silently have exercised the codeless branch. - Every pre-existing gate is retained: extension-readiness, the mtime-freshness design-time gate, the port squatter, the positive bounded-failure assertion and the negative assertion that the AzureWebJobsStorage modal never appears. - Unknown app kinds are fatal. A typo that quietly ran zero kinds would report success having tested nothing. CI changes - vscode-e2e-azurite gains a windows-latest sibling. It is a separate job rather than an os matrix because `needs` is declared per job, and a matrix would force the ubuntu leg to wait on setup-runtime-deps-windows for no reason. This follows how the codeful legs are already split instead of introducing a third pattern. - Both azurite jobs shard by app kind via a matrix, so wall-clock stays near the ~5 min single-kind baseline instead of doubling, and a failure names the kind that broke. Matrix legs roll up into one needs.<job>.result, so the summary gate needs no extra entries. - Both azurite jobs are BLOCKING and vscode-e2e-azurite-windows is added to the summary hard-fail chain. - Fixed a latent gap: the azurite job never set LA_E2E_SCENARIO_RETRIES, so the per-phase retry wrapper wired earlier in this PR was inert for azuriteonly. The la-runtime-deps cache path/key pair is untouched -- still exactly one distinct combination across the workflow, so the warm Windows and Linux caches survive. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5745044d-d3ac-4293-8c22-a66762e9170e * ci(vscode-e2e): size job timeouts from measured data instead of a blanket 45m Six jobs carried timeout-minutes: 45 regardless of what they actually cost. Measured across the last 25 workflow runs (successful jobs only -- a cancelled or timed-out job's duration is an artifact of the current limit, so folding it in would be circular): job worst healthy was now margin setup-extension-build 3.2m 15 10 3.1x vscode-e2e-compat 5.6m 25 15 2.7x vscode-e2e-windows 5.9m 45 20 3.4x vscode-e2e-azurite 6.6m 45 20 3.0x vscode-e2e-azurite-windows 8.5m 45 25 2.9x vscode-e2e-codeful-ubuntu 8.8m 45 25 2.8x setup-runtime-deps-windows 11.3m 45 25 2.2x vscode-e2e-codeful-windows 12.8m 45 35 2.7x setup-fixtures 14.2m 30 30 2.1x (unchanged) vscode-e2e (matrix) 16.1m 35 35 2.2x (unchanged) Worst-case runner burn across the workflow drops from 375 to 240 minutes. Deliberately NOT sized to the worst observed run. A job timeout is a backstop against a wedged runner, not a performance SLA, and these are UI-driven ExTester jobs with long but legitimate tails: over the same 25 runs, `vscode-e2e (activation-and-create)` has a median of 3.9m and a worst HEALTHY run of 14.9m -- a 3.8x tail. A limit set to "worst observed" during a fast week would convert those slow-but-green runs into red builds, which is precisely the failure mode the hang-guard work earlier in this PR exists to eliminate. Every value is therefore ~2x the worst healthy observation, which also absorbs: - the one fresh-session retry allowed by LA_E2E_SCENARIO_RETRIES, and - a COLD la-runtime-deps hydration. setup-runtime-deps-windows is continue-on-error, so its dependents must still fit when it fails and they hydrate from scratch (~+10 min on Windows). vscode-e2e-codeful-windows keeps the largest budget at 35m because 2x its worst run is already 25.6m. vscode-e2e-azurite-windows is sized generously for its measured cost because it has only ONE sample -- those legs are new in this PR. The fast-failure signal lives in the in-test guards (the debugInvoke hang guard, the bounded Azurite readiness wait), not in the job timeout. These limits only bound the damage when something wedges below those guards. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5745044d-d3ac-4293-8c22-a66762e9170e * fix(vs-code-designer): harden azurite setup found while reviewing #9470 Three defects surfaced by review of the readiness change, plus the legibility fix that prompted the investigation. - Refresh `azuriteLocationExtSetting` after the autostart prompts persist it. Flipping `autoStartAzurite` to `true` made the start block reachable on the same run for the first time, so the stale local silently overwrote the directory the user had just typed with the default. Not reachable before this branch; existing tests missed it because they mock Azurite as already running, which skips the start block entirely. - Re-raise `UserCancelledError` outside the inner telemetry scope in `pickFuncProcessInternal` and `startRuntimeApi`. The wrapper force-swallows cancellations -- it overrides `rethrow` to false regardless of what we set -- so dismissing an Azurite prompt fell through to `preDebugValidate` and re-opened the modal "Debug anyway" hang this change exists to prevent. - Tag the two terminal `executeOnAzurite` failures (extension missing, or failed to activate) as `AzuriteExtensionTerminalError` and give them precedence in the final readiness message. The retry loop, its bounds and its telemetry are deliberately unchanged: someone running Azurite via Docker or `npm -g azurite` with the extension merely disabled is still rescued by the probe. Only the message on a genuine timeout improves. Classification fails open, so an unrecognised error stays non-terminal. - Name the outer scope that owns the notification at both `suppressDisplay` call sites, and fix a test comment that named the wrong one. `suppressDisplay` is per-scope and exists to stop the nested scope double-showing what its parent already displays; `rethrow` is what makes Azurite failure terminal. Test mocks gained two fidelity fixes needed for the above to be provable: the telemetry mocks now model the library's cancellation force-swallow, and the `localize` mock now substitutes `{0}`/`{1}` so the terminal-cause message is distinguishable from the generic one. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 25bd30b3-5c06-4e3e-8ec0-f54bff4e4206 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5745044d-d3ac-4293-8c22-a66762e9170e Copilot-Session: 25bd30b3-5c06-4e3e-8ec0-f54bff4e4206
13 tasks
This was referenced Jul 31, 2026
Brian Lam (lambrianmsft)
added a commit
that referenced
this pull request
Aug 10, 2026
…efore blocking F5 (#9478) * fix(vscode): self-heal a provisioned-but-unrunnable func Core Tools before blocking F5 Extracted from #9462, which is now down to this one change: the codeful design-time DLL-lock fix and the cross-platform codeful E2E coverage landed in #9463, and the Azurite startup-race fix is already on main. Nothing on the F5 path distinguished "func.exe exists on disk" from "func.exe actually runs". `preDebugValidate` -> `validateFuncCoreToolsInstalled` -> `isFuncToolsInstalled()` spawns `func --version`; when that throws because the managed binaries are half-extracted, mid-reinstall, or restored from a poisoned runtime-deps cache, the only fallback was the interactive "You must have the Azure Functions Core Tools installed" modal. That modal is a dead end for a user mid-provision and is unanswerable headlessly, so debug just aborts. `validateFuncCoreToolsInstalled` now calls `attemptManagedFuncCoreToolsRepair()` first on the managed-binaries path: it silently reinstalls the managed binaries and re-verifies `func --version`, and only falls back to the interactive modal when the repair still can't produce a runnable func. Scoped to the managed (`useBinariesDependencies`) path only - system installs are untouched, since we don't own those binaries. Telemetry records `funcRepairAttempted`, `funcRepairSucceeded`, and `funcRepairError`. The E2E side of this work (`funcVersionRuns` executable-aware dependency gate) already merged with #9463, so this is product-only. Unit coverage: repair succeeds, repair runs but func still won't run, and the reinstall itself throwing - plus the `validateFuncCoreTools` opt-out path, which brings the changed file to 82% line coverage (over the pr-coverage gate). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * fix(vscode): guard concurrent func installs and keep the auto-repair silent Addresses review feedback on the extension-managed Functions Core Tools self-heal introduced in this PR. Concurrent-install race: `installFuncCoreToolsBinaries` had no in-flight guard, while `downloadAndExtractDependency` stages into a shared temp folder and `extractDependency` deletes and recreates the target folder. A failing `func --version` is exactly what you observe while another install is mid-extract, so the repair could start a second install on top of the activation-time one (`validateAndInstallBinaries` -> `validateFuncCoreToolsIsLatest`) and re-corrupt the tree. `installFuncCoreToolsBinaries` now tracks the in-flight install: a concurrent request for the same major version joins it, and a request for a different version waits for it to finish first. The repair path additionally checks `isFuncCoreToolsInstallInFlight()` and awaits the running install before re-probing instead of starting its own. Silent repair really is silent: the repair no longer reveals the output channel, and `downloadAndExtractDependency` accepts `suppressUi` so its three direct `showErrorMessage` calls don't fire for an automatic repair that already surfaces its own actionable prompt on failure. Output-channel logging and telemetry are unchanged, and the two download/checksum failures now log their message so suppressed failures stay diagnosable. User-initiated installs keep their existing UI. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * test(vscode): add E2E coverage for the func Core Tools pre-debug self-heal Adds scenario p414-funcrepair (Phase 4.14), the first end-to-end proof that a provisioned-but-unrunnable Azure Functions Core Tools binary is repaired by the pre-debug gate instead of dead-ending on the blocking install modal. Nothing exercised this path before: funcVersionRuns() in runtimeBinaryCheck.ts is a harness precondition gate that waits for the self-heal rather than proving it, and no test deliberately broke func and asserted recovery. funcRepair.test.ts: - Waits for the managed install and for two consecutive successful `func --version` probes. The second probe is the point: it proves activation-time validation has settled, so the repair observed later cannot be attributed to it. - Backs up and overwrites every managed func executable in place with a short marker line, so each file still exists and keeps its .exe extension and execute bit but no longer runs -- the real poisoned runtime-dependency cache state, not a simulation. The marker is deliberately non-empty and has no leading '#': glibc execvp falls back to /bin/sh on ENOEXEC, so an empty or comment-only file would exit 0 on Linux and the repro would prove nothing. The test asserts `func --version` actually fails before continuing. - Presses F5, which routes through pickFuncProcessInternal -> preDebugValidate -> validateFuncCoreToolsInstalled, the only caller of the gate. - Asserts, hard: the blocking "You must have the Azure Functions Core Tools installed" prompt never appears; `func --version` runs again; and the repaired file no longer holds the marker bytes (catching a probe that resolved some other binary). A liveness assertion requires the workbench scrape to have returned text at least once, so the negative assertion cannot pass vacuously. The "no suppressed-by-design error toast" check is soft, matching waitForRepairNotification in bundleRepair.test.ts. Writes are guarded by a containment check against the managed FuncCoreTools directory, and afterEach restores the original bytes wherever the repair did not already replace them, so a mid-test failure cannot poison the shared runtime-dependency cache for sibling scenarios. run-e2e.ts publishes the resolved dependency root as LA_E2E_RUNTIME_DEPS_ROOT, the same mechanism lspeperm already uses, so the test targets what the harness actually wrote instead of guessing a path and risking a developer's global func install. designerHelpers.ts exports and parameterizes the existing func path resolution (getManagedFuncCoreToolsDir / getFuncCoreToolsCandidatePaths / getFuncCoreToolsPath) instead of duplicating the layout. Default arguments keep every existing call site behaviourally identical. CI runs this on its own func-selfheal shard: it is the only scenario that downloads the Func Core Tools package twice, so folding it into suite-and-bundle risked pushing a retried shard past that job's 35 minute budget. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * test(vscode): anchor F5 debug context and fail fast when the func gate never runs The first CI run of p414-funcrepair failed for a harness reason, not a product one. The runner auto-opens a markdown preview, and it still held editor focus at F5 time: [depValidation] VS Code title at start: "Preview WBD-hybrid announcement.md - testws_ms7l55v8 (Workspace) - ..." startDebugging() -> focusEditor() focuses the active editor group, so "Debug: Start Debugging" ran with a non-project preview active. VS Code never resolved the Logic App folder's .vscode/launch.json, the pickProcess command was never evaluated, and preDebugValidate never ran. startDebugging() does not throw in that case, so the test sailed on and blamed the product for a repair it had never actually asked for. The tell was that the repair AND the blocking modal were both absent, and the whole job log contained no pickProcess or preDebugValidate evidence at all. Two fixes. anchorDebugContextOnWorkflow() gives F5 a Logic App debug context: clear stray editors (defaultContent -> closeAllEditors -> settle, SKILL.md rule 6), open the project's own workflow.json, then poll BOTH the window title and the active tab until each references it, and hard-fail otherwise. The assertion keys off the same window title whose value diagnosed the bug, so a silent no-op cannot get past it. Anchoring happens after the corruption so nothing can steal focus in between. workflow.json has no customEditors contribution, so this is a plain text editor and cannot reintroduce the webview-focus hazard it exists to fix. This uses the existing Quick Open helper rather than VSBrowser.openResources, which goes through `code -r` CLI IPC and is a silent no-op on headless Linux CI (designerHelpers.ts:499) -- the same class of failure being fixed here. waitForPreDebugGateEvidence() then proves F5 actually reached the product before committing to the 300 s repair wait, so "the harness never triggered the product" can no longer masquerade as "the product failed to repair". It watches four independent signals, all watermarked or pre-checked so they cannot match pre-F5 state: new output-channel lines from pickFuncProcessInternal's first two steps (activateAzurite, refreshConnectionKeys) or the repair's own download line; Azurite's blob port opening; the corruption marker disappearing; and the blocking prompt appearing, which counts as reached on purpose so the existing assertion reports it. On no evidence it fails immediately, names itself a harness failure, and dumps the window title and output tail. The debug toolbar, debug terminals and port 7071 are deliberately not used as liveness signals: they only appear after preDebugValidate returns, i.e. on the far side of a repair that can take minutes, so they cannot distinguish "not yet" from "never". Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * test(vscode): trigger func self-heal E2E debug via recorder extension The Phase 4.14 func self-heal scenario failed twice in CI because the debug session was never actually started. Both failures were harness-side, not product-side. Run 30549543233: the active editor at F5 time was an auto-opened markdown preview ("Preview WBD-hybrid announcement.md"), so VS Code never resolved the Logic App folder's launch.json. Run 30554041131: after closing all editors and re-opening workflow.json through Quick Open three times, the active editor was still settings.json. In both cases `startDebugging()` does not throw — it logs "Selecting: Debug: Start Debugging" and returns — so the test fell through and blamed the product for a repair it had never asked for. The command-palette F5 path resolves whichever launch configuration the ACTIVE EDITOR belongs to, and a headless CI session does not settle editor focus reliably. Replace it with the existing codeful task recorder extension, which calls vscode.debug.startDebugging(folder, configName) with an explicit folder: no editor focus, no Quick Open index, no palette. The product path is unchanged — that call runs the same resolution pipeline as F5, including the ${command:azureLogicAppsStandard.pickProcess} substitution that invokes pickFuncProcessInternal -> preDebugValidate -> validateFuncCoreToolsInstalled. - funcRepair.test.ts: drop anchorDebugContextOnWorkflow and the palette startDebugging call; add the recorder marker-file trigger plus a "debug start was never dispatched" fast-fail gated on the recorder's debugInvoke event. Gate on debugInvoke rather than debugStarted: for the codeless `attach` config, startDebugging only resolves after pickProcess finishes, which includes the repair being measured. Keep the existing gate-evidence check as a second-level harness/product discriminator, now also bailing on a late debugStartFailed. Stop the session via the recorder's stop-debug marker, with the palette path as fallback. - codefulTaskRecorderExtension/main.js: pick the debug folder and configuration by what launch.json actually contains (pickProcess -> logicapp -> first) instead of assuming workspaceFolders[0], and log the choice. generateLaunchJson emits exactly one configuration and the codeful shapes have no processId, so Phase 4.10 selects the same config as before. - run-e2e.ts: add a generic `recorder` scenario flag and opt p414-funcrepair in. Install once per scenario (prepareFreshSession only clears userDir) and reset the events/trigger pair per attempt so a retry never reads stale events. - SKILL.md: rule 18 plus §16 wire-up notes recording the failure signatures. Corruption, containment guard, restore logic and the assertion set are unchanged — the CI logs confirmed all of them behaved correctly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * test(vscode): answer the connectors prompt that blocks the func self-heal E2E The Phase 4.14 scenario reached the product correctly (the recorder logged debugInvoke with the right folder and config) but still timed out waiting for the repair. The screenshot artifact showed why: an unanswered QuickPick, "Enable connectors in Azure for Logic App <name>", parked execution inside refreshConnectionKeys (pickFuncProcess.ts:114) -- which runs BEFORE preDebugValidate (:119). validateFuncCoreToolsInstalled was therefore never called, so there was neither a repair nor the install modal. Azurite was healthy throughout. - Poll dismissConnectorsPromptIfVisible() on every iteration of both waitForPreDebugGateEvidence and watchForFuncRepair. The prompt appears asynchronously after the debug start, so a one-shot pre-dismissal would race. Escape is a safe answer: azureConnectorWizard.ts catches isUserCancelledError and resolves to 'no', the same as "Skip for now". - Scope the dismissal by text so it cannot swallow the assertion under test. The func prompt is showWarningMessage({ modal: true }) -> .monaco-dialog-box, while the shared helper only queries .quick-input-widget; the wrapper also text-matches first, and both call sites refuse to run it once the blocking prompt has been seen. Non-matching QuickPicks are logged, not dismissed. - Pin readOutputSinceWatermark to watermark.filePath. It previously re-ran the "newest log by mtime" search per call, so an unrelated file could become newest and its entire contents were reported as new output -- that is why the failure dump printed a node tarball listing and hid this stall for a full CI cycle. logOutputSinceWatermark now dumps the last 8 KB of the new region only. - Track PAST_CONNECTION_KEYS_MARKERS so a timeout says whether the flow reached the gate at all, distinguishing a stall before the gate from a real repair failure. - SKILL.md rules 19 and 20. Test-only change; no product code touched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * test(vscode): add a Windows CI leg for the func self-heal E2E The func self-heal had zero Windows signal: test.yml, pr-coverage.yml and private-vsix-build.yml are all runs-on: ubuntu-latest, so neither the unit tests nor the p414-funcrepair E2E ever ran there. That gap skews wrong for this feature -- the real-world causes of an unrunnable managed func (AV quarantine, partial extraction, an orphan func.exe holding a file lock) are disproportionately Windows, and binaries.ts:224-243 reasons explicitly about EPERM/EBUSY from a locked func.exe. New blocking job vscode-e2e-funcselfheal-windows (windows-latest), wired into the vscode-e2e-summary gate with no continue-on-error: - Depends on setup-runtime-deps-windows. A cold la-runtime-deps-Windows-v1 cache made the codeful Windows leg fail 100% of the time by hydrating underneath the running test, and this scenario downloads Func Core Tools twice, so it is the most cold-cache-sensitive job in the workflow. - Creates fixtures on the runner via p41a-fixtures rather than consuming the workspace-fixtures artifact. That artifact is ubuntu-bound: the manifest stores absolute paths (workspaceManifest.ts:27-58) written under the ubuntu runner's RUNNER_TEMP, and nothing rebases them, so on Windows every entry points at a nonexistent Linux directory. No existing Windows job consumes it. - Cache is restore-only. This job deliberately corrupts the managed func; a crashed session could otherwise save a poisoned tree under an exact-key hit that later runs can never repair. - Adds a manifest guard step. funcRepair.test.ts calls this.skip() when there is no Standard/Stateful entry, which would report a passing job with zero coverage -- the likeliest bad outcome on a brand-new leg. - timeout-minutes: 45, sized from setup-runtime-deps-windows's observed 11.3 min max for p41a-fixtures plus ~12 min for the scenario, doubled for the LA_E2E_SCENARIO_RETRIES retry. Harness fix (genuine bug, not wiring): prepareFreshSession()'s Windows branch left the VS Code kill unguarded while Linux guards every pkill individually, so a PowerShell hiccup or the 10s timeout threw past the func kill into the outer catch, which only logs "kill failed - continuing". Invisible on Linux, load-bearing here: p41a-fixtures runs with autoStartDesignTime: true and leaves a live design-time func.exe, and p414-funcrepair then overwrites func.exe in place. Each kill is now guarded, plus a bounded 15s waitForWindowsFuncProcessesToExit() detection poll, since Stop-Process only signals. Log-only, and it reports a failed probe distinctly from "all clear". Also adds describeRunningFuncProcesses() to the overwrite-failure message (Windows-only, failure-path only) so an EPERM names the process holding the handle, and SKILL.md sections 18 (closing a dangling "rule 18" reference this PR left in two files) and 19 (Windows CI porting rules). Test-only change; no product code touched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * fix(vscode): log the silent func Core Tools repair so failures are diagnosable attemptManagedFuncCoreToolsRepair runs silently in the middle of F5 and, with suppressUi on, wrote nothing anywhere. When it fails the user sees a debug session that simply does nothing, and there is no trace to explain why. The Windows E2E leg hit exactly that: the pre-debug gate was reached (refreshConnectionKeys logged its skip) but the managed func was never repaired, no install modal appeared, and the extension wrote zero further bytes to its output channel. With no breadcrumbs it is impossible to tell whether the repair ran and failed, or was never entered at all. Add output-channel breadcrumbs at the repair's entry, the "an install is already in flight, wait for it" branch, and both exits. These are the same lines a user would need when reporting "F5 does nothing", so they earn their place independently of the test. Test-side, the failure path now also lists every "Azure Logic Apps" output log with sizes and dumps the newest non-pinned one. The liveness gate pins a single log file on purpose, but that pinning is blind to VS Code starting a new output_logging_* folder, which would make "the extension wrote NOTHING" a false statement. Running func processes are reported on the repair-timeout path too, not just on the overwrite-failure path. Unit tests mock ext.outputChannel; 14/14 pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * fix(vscode): bound the func --version probe so a hanging binary can't stall F5 The pre-debug gate probes `func --version` through `tryExecuteCommand`, which only settles on 'close'/'error'. A managed func that hangs instead of exiting therefore stalls `validateFuncCoreToolsInstalled` indefinitely: the self-heal never runs, no install modal appears, nothing is logged, and F5 silently does nothing. The spawned process also keeps a handle on the binary, so a later attempt to replace it fails with EBUSY. That is exactly the state the Windows E2E leg reproduced. The gate was reached (the connector refresh logged its completion immediately before it), the deliberately corrupted func was never repaired, no modal appeared, the output channel went completely silent, and the retry could not overwrite func.exe because it was still locked — which only something spawned from it could do, since the debug task never started. - Add `executeCommandWithTimeout` to cpUtils, which terminates a command that outlives its budget. Commands are spawned with `shell: true`, so the tracked pid is the shell; on Windows the whole tree is taken down with taskkill (matching startDesignTimeApi/funcHostTask) because killing only the shell leaves the real command running and still holding its file handles. - Probe func with a 60s bound and treat a hang exactly like a failure, so the existing repair runs. The bound is generous so a genuinely slow first run (on-access AV scanning a freshly extracted binary) is not mistaken for a broken one. - Log why the probe failed. It is the only signal distinguishing "func is broken" from "the gate never ran" when debug appears to do nothing. Unit tests cover the Windows and non-Windows kill paths, the clean-exit path, non-zero exits, that tryExecuteCommand stays unbounded, and that a timed-out probe still triggers the repair. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * fix(vscode): simplify func self-heal flow Fold the managed Functions Core Tools repair into the binaries validation path, remove bespoke UI suppression plumbing from the installer/download helpers, and keep user-facing output/error prompts owned by explicit install branches. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * chore(vscode): drop unrelated main-formatting deltas Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * fix(vscode): register commands before dependency startup Register extension commands before activation-time dependency validation and design-time startup so E2E setup and users cannot invoke a command before it exists. Add an explicit dependency-validation command gate in the fixture setup path so command-registration races fail with direct diagnostics instead of later webview visibility timeouts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * fix(vscode): force bundle recheck during dependency validation Explicit runtime dependency validation now bypasses the per-session bundle health cache so a tampered extension bundle is re-read from disk and synchronously repaired. This keeps the startup/design-time cache fast while making the user-invoked validation command deterministic for bundle repair. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * test(vscode): stabilize func self-heal e2e setup Invoke the product dependency validation command before the func self-heal test waits for managed FuncCoreTools, so the scenario does not depend on activation-time provisioning. Shorten the Mocha suite/test names to keep generated failure screenshot paths extractable on Windows. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * test(vscode): avoid destructive func repair setup Skip the explicit dependency validation command when the managed Func Core Tools binary is already runnable. CI preflight had already provisioned FuncCoreTools, and re-running validation could remove that folder during a failed revalidation before the test reached the self-heal scenario. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 * test(vscode): align NuGet E2E settings expectation Main now generates azureFunctions.preDeployTask for converted NuGet projects. Update the new NuGet debug lifecycle E2E expectation to match the project-consistency generator while keeping this PR's func self-heal mode intact. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f6808401-494f-4b39-99a2-aa213d886ae0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Commit Type
Risk Level
Product change is narrowly scoped to codeful projects' design-time host configuration; non-codeful (codeless) behavior is byte-for-byte unchanged and is covered by existing tests. The larger part of the diff is CI/E2E.
What & Why
Symptom
Open a codeful workflow project, let it fully load, press F5. The build fails with
MSB3026:Killing the two lingering
func.exeprocesses makes debug work. Not reproducible on 5.961.19; codeless is unaffected.Root cause
The design-time host is
func host startfromworkflow-designtime, configured withProjectDirectoryPath = <projectRoot>. Two changes composed into a regression:WorkerRuntime.NodetoWorkerRuntime.Dotnet+FUNCTIONS_INPROC_NET8_ENABLED=true, so the Data Mapper Test panel could spawn the NetFxWorker. Under Node the design-time host never loaded the project's compiled assemblies. Under in-proc .NET 8 it loads them fromlib/codeful— and locks them.workflow-designtimefor source-controlled projects, so the host is now reliably running on open. Necessary for the lock to appear, but not the flip that caused it.pickFuncProcess.tsis byte-identical across both tags — it only callswaitForPrevFuncTaskToStop, which consults the VS Code task map and never stops the design-time host. So the ordering bug was always latent; #9410 is what made it bite.Fix
Codeful projects pin the design-time host to the Node worker via a new
shouldUseNodeDesignTimeWorker(projectPath, logicAppType?)helper. Node never loadslib/codeful, so nothing is locked,CopyToCodefulFoldersucceeds, and there is no per-debug kill/restart of the design-time host. Codeful does not need the dotnet NetFxWorker — that exists for the Data Mapper Test map XSLT, which is not a codeful design-time feature.Applied in both places that decide the worker runtime:
regenerateDesignTimeDirectory— generates Node settings for codeful.validateDesignTimeDirectory— an existing codeful design-timelocal.settings.jsonstill on dotnet is now treated as invalid and regenerated, so projects already broken by 5.98x self-heal on the next design-time start.Why this shipped: the codeful debug E2E had no CI presence at all
Not a deliberate skip — an architectural gap. The declarative
scenarios[]bootstrapper only runs test files with a generic workspace spec, and codeful debug is a bespokerunCodefulDebugPhases()that creates its own modern + legacy codeful workspaces, patches the generated.csprojtargets, installs a task-recorder extension and sets ~6LA_E2E_CODEFUL_*env vars. None of that is expressible as ascenarios[]row, so it only ran viaE2E_MODE=fulllocally. On top of that the matrix runners had no .NET SDK, and the bug was believed Windows-only while CI was Ubuntu-only.Under this fix the regression signal is deterministic and cross-platform (design-time
FUNCTIONS_WORKER_RUNTIME=node, noFUNCTIONS_INPROC_NET8_ENABLED), so it no longer needs a file-lock to reproduce. The codeful suite now runs on ubuntu-latest and windows-latest.Supporting CI work
setup-runtime-deps-windowsseedsla-runtime-deps-Windows-v1, which previously could never be written: no Windows job both hydrated the deps and succeeded, andactions/cacheispost-if: success(). A deadlock. Windows codeful therefore hydrated dependencies underneath the already-running test — the real cause of itsbuildStart:0failures, not a hosted-runner func-exec gap (func --versionsucceeds on the runner every run).deps-checkthat actually executesfunc --version. A half-hydrated tree is worse than no cache: it restores as an exact hit forever and thecache-hit != 'true'guard suppresses any repair.func --versionto exit 0 rather than checking existence + execute bit, matching what the product's pre-debug gate actually spawns.Cache
pathandkeyare deliberately byte-identical to the existing entries —actions/cachederives the entry version from the path set, so editing it would silently cold-start the Ubuntu leg.Impact of Change
func.exe. Projects already broken by 5.98x self-heal on the next design-time start. Codeless is unaffected.shouldUseNodeDesignTimeWorkerhelper. Codeful debug is now exercised in CI on both OSes.la-runtime-deps-Windows-v1, 1006 MiB); repo usage verified at 3.86 GB / 10 GB withla-runtime-deps-Linux-v1intact.Test Plan
validateProjectArtifacts, 34connectionPanel): codeful → Node worker; an existing dotnet codeful design-time file → invalid → regenerated; non-codeful → dotnet unchanged.FUNCTIONS_WORKER_RUNTIME=nodeandbuildExit === 0, plus modern (AfterTargets="Build;Publish"→ publish skipped) and legacy (AfterTargets="Publish"→ publish runs) template variants.vscode-e2eon ubuntu-latest and windows-latest. Four consecutive all-green runs on the fork (30298228591,30305533988,30307633190,30315114458) with byte-identical assertion signatures. Local: Biome clean,tsup --config tsup.e2e.test.config.tsbuilds, workflow YAML parses.Gating posture — please read before reviewing the workflow
Both codeful legs are advisory (
continue-on-error: true, excluded from thevscode-e2e-summaryhard gate). This is deliberate and is the direct consequence of splitting the Azurite fix out:worker=node, this PR's actual regression guard — passes on every observed run, cold or warm, on both OSes.preDebugValidate→validateEmulatorIsRunningraces Azurite startup and pops a headless-unanswerable "Failed to verify AzureWebJobsStorage connection" modal, cancelling the debug session before any task is dispatched (symptom: 12-minute F5 wait,events captured=1,buildStart:0). Byte-identical code has produced both green and red runs purely on that race.Gating on that today would make a required check flaky. Promoting both legs to blocking is the concrete deliverable of the follow-up Azurite PR.
Known gaps (stated deliberately)
connections.jsonwas also reported on 5.981.0. I could not reproduce a clobbering write path:createConnectionsJsonwrites{}when the file is absent, so{}on a fresh codeful project is expected, andsaveConnectionReferencesonly writes when there is at least one key. The consume path is hardened with unit tests here, but no root cause is claimed.openLanguageServerConnectionViewis declared"when": "never"); it is dispatched by the Logic Apps language server against.csfiles, so an E2E must drive the CodeLens with the LSP running.Contributors
Brian Lam (@lambrianmsft) — reported the regression, identified the design-time worker runtime as the likely trigger, and chose the Node-worker approach over per-debug host restart.
Screenshots/Videos
N/A — no UI changes.