Skip to content

fix(vscode): Guard against bundle download corruption #9294

Merged
lambrianmsft merged 52 commits into
Azure:mainfrom
lambrianmsft:lambrianmsft/verify-dependency-downloads
Jun 26, 2026
Merged

fix(vscode): Guard against bundle download corruption #9294
lambrianmsft merged 52 commits into
Azure:mainfrom
lambrianmsft:lambrianmsft/verify-dependency-downloads

Conversation

@lambrianmsft

@lambrianmsft lambrianmsft commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Commit Type

  • feature - New functionality
  • fix - Bug fix
  • refactor - Code restructuring without behavior change
  • perf - Performance improvement
  • docs - Documentation update
  • test - Test-related changes
  • chore - Maintenance/tooling

Risk Level

  • Low - Minor changes, limited scope
  • Medium - Moderate changes, some user impact
  • High - Major changes, significant user/system impact

What & Why

Microsoft.Azure.Functions.ExtensionBundle.Workflows is downloaded from a CDN at extension activation. When the CDN occasionally returns a truncated zip, the extension cached the partial file and failed later in opaque ways. There was also no way to test against an unreleased bundle locally, no fallback when a private/experimental CDN was misconfigured, and no user-visible signal during activation downloads.

This PR adds end-to-end integrity verification (size + MD5), opt-in experimental bundle settings with a public-CDN safety net, and withProgress notifications so the user sees what is happening (and why a redownload is occurring).

Impact of Change

  • Users: Bundles are verified against Content-Length and Content-MD5 on download, and against an MD5 sidecar on every activation. Corrupt local copies are detected and replaced automatically. Users see a progress notification during downloads and a warning toast immediately before a corruption-triggered redownload, so silent activation hangs are gone. Three new VS Code settings (useExperimentalExtensionBundle, experimentalExtensionBundleSourceUri, experimentalExtensionBundleVersion) let users point at unreleased bundle builds without losing the public-CDN safety net. Bundles are now verified and repaired automatically for users.
  • Developers: New downloadFileWithVerification, verifyLocalBundleHash, fetchExpectedMd5, and isMissingPackageError helpers in integrity.ts. bundleFeed.ts gains a DownloadReason-driven withProgress wrapper used at every download call site, plus a private-to-public CDN fallback chain that only triggers on "package missing" errors (4xx / DNS), never on integrity failures. New E2E phase bundleintegrityonly exercised via run-e2e.js.
  • System: One extra HEAD request per activation to validate the local sidecar. No change to the public CDN contract. The MD5 sidecar lives next to the bundle so a manually-deleted bundle directory self-heals.

Test Plan

  • Unit tests added/updated
  • E2E tests added/updated
  • Manual testing completed
  • Tested in: VS Code extension host (Windows), public Azure CDN, simulated experimental source override. 66 bundleFeed unit tests + 1065 vscode-designer extension-unit tests passing. New bundleintegrityonly live E2E phase 4/4 passing.

Contributors

Screenshots/Videos

Copilot AI added 6 commits June 18, 2026 12:36
The extension bundle and other CDN-hosted dependencies were streamed to disk without verifying what arrived. Truncated or corrupted downloads silently extracted, mainly affecting Microsoft.Azure.Functions.ExtensionBundle.Workflows.

* Add downloadFileWithVerification: streams the file, counts bytes, computes MD5 inline, and asserts both against the response's Content-Length and Content-MD5 headers. Azure Blob Storage (which backs cdn.functions.azure.com) sets these on upload, so this catches CDN-edge corruption end-to-end with no publishing-pipeline changes.
* Wrap downloads in a retry loop (default 3 attempts, exponential backoff). Retries network errors, 5xx responses, and DownloadIntegrityError; surfaces 4xx as fatal.
* Fix latent bug in downloadAndExtractDependency: the axios.get(...).then(...) chain was never awaited, so callers' awaits resolved before the download started and write errors were not propagated. Now downloads complete before extraction, and errors bubble up to callers.
* Telemetry: <dep>DownloadAttempts, ExpectedSize, ActualSize, Md5Match (true/skipped), DownloadDurationMs per attempt.
* Treat missing Content-Length / Content-MD5 as advisory so non-Blob mirrors keep working.

Applies to all callers of downloadAndExtractDependency (extension bundle, Func Core Tools, Node.js, .NET install script).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rification, CDN E2E probe

Phase 2-4 of the bundle integrity work.

* Adds 3 azureLogicAppsStandard settings: useExperimentalExtensionBundle,
  experimentalExtensionBundleSourceUri, experimentalExtensionBundleVersion.
  When the toggle is on, downloadExtensionBundle never calls the public CDN
  for `latest'' so a local/preview bundle isn't silently overridden.
* Extracts a vscode-free integrity helper (app/utils/integrity.ts) so the
  same verifier is used by binaries.ts and the new E2E probe.
* On-disk hash verification: each download writes a sidecar
  .bundle-source-md5; on next startup we HEAD the public bundle zip and
  redownload if the sidecar is missing or stale.
* New E2E phase (bundleCdnHealth.test.ts, E2E_MODE=bundleintegrityonly):
  pure-Mocha probe that fails loudly if the CDN ever stops emitting
  Content-Length / Content-MD5. Wired into the independentonly shard.
* writeTestSettings() in run-e2e.js gains experimental-bundle options.
* SKILL.md and docs/ai-setup/packages/vs-code-designer.md updated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- bundleFeed: use membership test (localVersions.some(v === envVarVer))
  instead of equality with latestLocalBundleVersion so the env-var pin
  is honored when on disk alongside other versions.
- bundleFeed: add tryDownloadBundleAndWriteSidecar / tryFetchSourceIndex
  helpers that swallow 'package not present' errors (HTTP 4xx, network
  failures from isMissingPackageError) and surface them as fallback
  signals rather than fatal exceptions.
- bundleFeed: when toggle is on, the experimental sourceUri is now
  attempted first (with index probe to detect pinNotInIndex), then
  falls back to the public CDN for the same pin / latest. Both source
  and public failing throws so the dev sees the failure.
- bundleFeed: experimentalSourceFallback telemetry property records
  pinNotInIndex | zip404 | index404 | networkError reasons.
- bundleFeed: 'fall through to public' path now downloads from
  PUBLIC_BUNDLE_BASE_URL, not the broken experimental baseUrl.
- package.json: reword the three experimental settings to call out the
  master-toggle gating and the public-CDN safety net.
- integrity: export isMissingPackageError shared helper.
- bundleFeed.test: 8 new tests covering env-var membership + fallback
  matrix (zip404 / pinNotInIndex / index404 / networkError / both
  CDNs failing / toggle off ignores experimental settings).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…+ toasts

When the extension activates and decides to (re)download the Logic Apps
extension bundle, the user now sees a notification explaining what's
happening rather than waiting silently while activation hangs.

- bundleFeed: new downloadBundleWithProgress / tryDownloadBundleWithProgress
  helpers wrap every download call in vscode.window.withProgress with a
  context-specific title that names the version and the source CDN
  (public Azure CDN vs the configured private/experimental URL).
- bundleFeed: when verifyLocalBundleHash returns sidecarMissing /
  sidecarMismatch we now showWarningMessage *before* redownloading so
  the user understands why the bundle is being replaced even though
  they didn't change anything.
- bundleFeed: success toast (showInformationMessage) on completion.
- bundleFeed: all download call sites (env-var pin, experimental pin,
  experimental cold-start, public-CDN fallback, public newer-version,
  corrupt-local redownload) now go through the new helpers.
- bundleFeed.test: extend vscode mock with window.withProgress (calls
  task immediately), showWarningMessage, showInformationMessage, and
  ProgressLocation. Two new tests assert the corruption warning + the
  newer-version progress notification render with the expected copy.

64 → 66 bundleFeed tests, 1063 → 1065 extension-unit tests passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Future sessions opening a PR must read .github/pull_request_template.md
(lowercase filename) before writing the body. Promote this from the
generic 'opening a PR body' row to its own top-of-table row, and add it
as Hard Rule #1 so it cannot be skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…at applies

Add explicit rule to release-scribe pattern in review-patterns.md: when
authoring a PR body, copy the entire template structure (every section,
every checkbox, every HTML comment hint) and only flip applicable boxes
to [x]. Never delete unticked rows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 18, 2026 22:57
@lambrianmsft lambrianmsft added the risk:medium Medium risk change with potential impact label Jun 18, 2026
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🤖 AI PR Validation Report

PR Review Results

Thank you for your submission! Here's detailed feedback on your PR title and body compliance:

PR Title

  • Current: fix(vscode): Guard against bundle download corruption
  • Issue: None blocking. The title is specific and clearly describes the change.
  • Recommendation: Optional: remove the trailing space for polish.

Commit Type

  • Properly selected (fix).
  • Only one commit type is selected, which matches the title prefix.

⚠️ Risk Level

  • The PR body marks Medium, but the code diff introduces broad integrity verification, new settings, fallback behavior, activation-time behavior changes, and multiple new E2E paths. That reads as High risk from the diff.
  • Recommendation: Update the PR body risk selection to High and ensure the repo label matches (risk:high). The current risk:medium label/body combination does not match the scope of the change.

What & Why

  • Current: Clear explanation of the corruption problem, why the cached partial zip was bad, and what the PR adds.
  • Issue: None.
  • Recommendation: None required.

Impact of Change

  • The impact section is detailed and aligns with the diff.
  • Recommendation:
    • Users: Good.
    • Developers: Good.
    • System: Good.

Test Plan

  • The diff includes both unit tests and E2E tests, so the plan passes.
  • Manual testing is also documented clearly.

⚠️ Contributors

  • The section is present but empty.
  • Recommendation: If others helped with design, review, or implementation, add them here as @username mentions. If not, this can remain blank.

Screenshots/Videos

  • No screenshots/videos are necessary for this change as it is mostly backend/behavioral and test-related.
  • Recommendation: None.

Summary Table

Section Status Recommendation
Title Optional cleanup: remove trailing whitespace
Commit Type None
Risk Level ⚠️ Change to High; label/body should match
What & Why None
Impact of Change None
Test Plan None
Contributors ⚠️ Add contributors if applicable
Screenshots/Videos None

Overall: the PR body mostly passes, but the advised risk level is higher than the current selection. Please update the risk selection/label to High before merging.


Last updated: Mon, 22 Jun 2026 18:29:46 GMT

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the VS Code designer extension’s activation-time download of Microsoft.Azure.Functions.ExtensionBundle.Workflows by adding integrity verification and improving the developer/testing story around experimental bundle sources. It also adds a new Mocha-only E2E phase to continuously validate the public CDN’s integrity headers and exercise the verifier logic without spinning up VS Code.

Changes:

  • Added CDN download integrity verification (size + MD5) plus a local MD5 sidecar to detect and self-heal corrupt cached bundles on subsequent activations.
  • Introduced opt-in experimental bundle settings with a “private source → public CDN” fallback that triggers only for missing-package style failures.
  • Added a Mocha-only E2E probe (bundleintegrityonly / Phase 4.11) and documented the new phase in the VS Code E2E skill docs.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs/ai-setup/packages/vs-code-designer.md Documents new E2E phase 4.11 and E2E_MODE=bundleintegrityonly.
apps/vs-code-designer/src/test/ui/SKILL.md Adds the new CDN integrity probe test to the VS Code E2E “skill” inventory and mode table.
apps/vs-code-designer/src/test/ui/run-e2e.js Adds a Mocha-only phase runner plus a fast-path mode for bundleintegrityonly, and wires the probe into independentonly.
apps/vs-code-designer/src/test/ui/bundleCdnHealth.test.ts New live CDN probe validating Content-Length/Content-MD5 on the bundle zip and a small verified download.
apps/vs-code-designer/src/package.json Adds three new VS Code settings for experimental bundle selection and source URI/version.
apps/vs-code-designer/src/constants.ts Adds setting keys and the MD5 sidecar filename constant.
apps/vs-code-designer/src/app/utils/integrity.ts New vscode-free download verifier + helper utilities (fetchExpectedMd5, retry logic, missing-package classification).
apps/vs-code-designer/src/app/utils/bundleFeed.ts Integrates verification, sidecar checks, experimental source resolution + fallback chain, and user-visible progress/toasts.
apps/vs-code-designer/src/app/utils/binaries.ts Routes all dependency downloads through the verifier and propagates integrity results/telemetry.
apps/vs-code-designer/src/app/utils/test/bundleFeed.test.ts Updates/adds unit tests for sidecar verification, progress/warning UX, and experimental fallback behavior.
apps/vs-code-designer/src/app/utils/test/binaries.test.ts Adds unit tests for downloadFileWithVerification behavior (MD5/size verification, retry rules, telemetry).
.squad/knowledge/review-patterns.md Tightens PR-body guidance: preserve template sections/checkboxes verbatim.
.squad/knowledge/INDEX.md Updates knowledge index triggers and hard rules around PR body editing.

Comment thread apps/vs-code-designer/src/app/utils/integrity.ts
Comment thread apps/vs-code-designer/src/app/utils/integrity.ts
Comment thread apps/vs-code-designer/src/app/utils/bundleFeed.ts Outdated
Comment thread apps/vs-code-designer/src/app/utils/bundleFeed.ts
Comment thread apps/vs-code-designer/src/test/ui/run-e2e.js Outdated
…esponses

Reported by user: every dotnet SDK install fails 3x with
  Download integrity check failed for https://dot.net/v1/dotnet-install.ps1:
  size mismatch (expected 24942, got 76680).

Root cause: dot.net/v1/dotnet-install.ps1 is served gzipped by the CDN.
axios (Node) sends Accept-Encoding: gzip, ... by default and auto-decompresses
the stream. Our Content-Length check then compared the gzipped header value
(24942) against the decoded bytes piped to disk (76680) and threw
DownloadIntegrityError on every attempt.

This is a real regression introduced by Phase 5: dotnet install + any text
dependency the CDN gzips at the edge has been broken since that ship. Bundle
zips (binary, not gzipped) were not affected, which is why E2E missed it.

Fix:
- integrity.ts: force Accept-Encoding: identity + decompress: false on the
  axios GET so Content-Length reflects the actual transferred bytes.
- integrity.ts: defensive — if the server returns Content-Encoding anyway,
  skip the size check (Content-Length is meaningless next to decoded bytes).
  MD5 is still verified when present.
- integrity.ts: same Accept-Encoding: identity on the fetchExpectedMd5 HEAD.

Tests:
- binaries.test.ts: 3 new cases — (a) gzip response with mismatched
  Content-Length succeeds, (b) un-encoded mismatched Content-Length still
  throws DownloadIntegrityError, (c) Accept-Encoding: identity header is
  sent on every request.
- bundleCdnHealth.test.ts: new live E2E case that round-trips
  dot.net/v1/dotnet-install.ps1 end-to-end via downloadFileWithVerification
  and sanity-checks the script body. Catches future CDN/axios drift on the
  actual URL we install from.

109 binaries+bundleFeed unit tests passing. 5/5 bundleintegrityonly live
E2E tests passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI added 6 commits June 18, 2026 17:04
verifyLocalBundleHash only compared the stored sidecar string to the CDN's HEAD
Content-MD5, so any post-download mutation of the extracted bundle on disk (manual
edits, disk bit-rot, AV restore, partial overwrite, interrupted install) silently
passed verification. The user can end up running a corrupt 400 MB bundle indefinitely.

Upgrades the sidecar to JSON { version, sourceMd5, contentHash } and adds
computeBundleContentHash — a deterministic streaming sha256 over the extracted tree
(POSIX-normalized relative paths, sidecar self-excluded, <relPath>\\0<size>\\0<bytes>\\0
framing). verifyLocalBundle now recomputes that hash on every activation before it
falls through to the existing publisher-MD5 HEAD check.

New result 'contentMismatch' joins the existing Phase 6 corruption-notification path:
warning toast + progress notification + redownload. Legacy bare-MD5 sidecars (and JSON
sidecars missing contentHash) collapse into 'sidecarMissing' so the one-time migration
stays silent — those users haven't actually hit a corruption event.

Rejected alternatives (kept here for future readers):
- range-GET the zip central directory from the CDN: regresses offline activation
- keep source.zip on disk: ~400 MB per cached version, 1+ GB redundancy with 2-3 cached

Unit coverage: 7 new tests in bundleContentHash.test.ts (determinism, byte/add/remove
sensitivity, sidecar exclusion, path sensitivity) + 3 new full-pipeline cases in
bundleFeed.test.ts (legacy migration silent, JSON contentHash mismatch fires toast +
redownload, matching JSON sidecar stays 'passed'). 1078 unit tests pass, 5/5
bundleintegrityonly E2E pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…dle download

main.ts fires downloadExtensionBundle as a background task during activation so
the UI stays snappy. When the bundle is corrupt or missing, the re-extract can
take 10+ seconds — and if the user opens a workflow during that window,
startDesignTimeApi spawns func.exe against the half-extracted bundle folder.
On Windows that locks the folder, kills the design-time host, and leaves the
bundle in a half-extracted state until the next activation.

Observed in the wild:
  5:48:24 PM: Re-downloading Logic Apps extension bundle 1.165.52...
  5:48:28 PM: Starting Design Time Api...   ← user opened workflow
  5:48:29 PM: Stopping Design Time Api...   ← crashed
  Successfully downloaded ...               ← download finally finished

Fix: add a module-level in-flight tracker in bundleFeed.ts. downloadExtensionBundle
dedupes concurrent callers and exposes:
  - waitForExtensionBundleReady(): blocks on the in-flight work (noop otherwise)
  - isExtensionBundleDownloadInFlight(): sync probe for log-gating

startDesignTimeApi awaits waitForExtensionBundleReady() immediately before spawning
func.exe, with a one-line output-channel message so the user sees what's happening.
main.ts also gets a .catch() so background download failures surface in the log
instead of becoming unhandled rejections.

New unit test: concurrent downloadExtensionBundle calls dedupe (only one
downloadAndExtractDependency call), waitForExtensionBundleReady blocks until that
call completes, and the in-flight flag clears afterward. 1079/1079 unit tests pass.
5/5 bundleintegrityonly E2E pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After downloadFileWithVerification confirms the zip itself, AdmZip.extractAllTo
could leave a partial tree on disk if a file lock, antivirus quarantine, or disk
pressure interrupted extraction mid-stream. We then wrote a content-hash sidecar
over the partial tree, blessing the broken state as 'good'. Subsequent
activations passed the sidecar check while func.exe failed to load missing
assemblies.

Phase 11 closes the gap:

- New verifyExtractedZip walks the zip's central directory after extraction and
  asserts every non-directory entry exists on disk at the expected size. Throws
  BundleExtractionError with a discriminator (missing | sizeMismatch | empty).
- extractDependency wraps cleanDirectory + extractAllTo + verifyExtractedZip in
  a 2-attempt retry loop that absorbs transient EBUSY/EPERM/EACCES locks and
  verification failures with a 750ms backoff.
- downloadAndExtractDependency now tears down the partial targetFolder if
  extraction throws, so the next activation re-downloads from scratch instead
  of inheriting a corrupt tree.
- downloadBundleAndWriteSidecar refuses to write a sidecar when the content
  hash is undefined (bundleDir vanished post-extract).

Tests: 5 new unit cases in binaries.test.ts covering happy path, missing file,
truncated file, empty extract dir, and empty zip. 1084/1084 vscode-designer
unit tests pass; bundleintegrityonly E2E (5/5) green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…terminate orphan func.exe

When a corrupt extension bundle is detected, the existing re-extract
pipeline failed in two ways on Windows:

1. AdmZip's overwrite mode only replaces files that exist in the new
   zip. Stale files from the prior version remained on disk, so the
   tree was a hybrid of new + old content even when extract appeared
   to succeed.

2. When an orphan func.exe from a previous VS Code session held bundle
   DLLs open, cleanDirectory + extractAllTo hit EPERM, the prior catch
   block's rm-rf was silently swallowed, and activation continued
   against the still-corrupt directory. Users saw 'No job functions
   found' and unhealthy storage forever.

Fix:

- extractDependency now does fs.rmSync(targetFolder, recursive:true,
  force:true) BEFORE extraction, so we always extract into a truly
  empty directory. Stale files cannot survive the replace.
- If the delete throws EPERM/EBUSY/EACCES (i.e. the dir is locked),
  downloadAndExtractDependency's catch invokes a new helper,
  offerToTerminateOrphanFuncProcesses, that:
    a) enumerates running func.exe PIDs via tasklist (Windows only)
    b) filters out ones tracked by ext.designTimeInstances
    c) prompts the user with a 'Terminate N process(es)' button
    d) calls process.kill on accepted orphans, waits 1.5s
- After successful termination, extractDependency is retried once.
- Drop the prior destructive cleanup in the failure catch — leaving
  the user with the prior (corrupt-but-locked) bundle is strictly
  better than leaving them with no bundle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eady

After the orphan-func + delete-first fix, users hit a new symptom: the
bundle download visibly completed and the bundle was on disk, but the
design-time host was stuck forever on 'Waiting for Logic Apps extension
bundle download to complete'.

Root cause: the in-flight promise gate self-deadlocks.

  downloadExtensionBundle()
    inFlightBundleWork = new Promise(...)        // resolves only in finally
    downloadExtensionBundleCore()
      downloadAndExtractDependency()
        extractDependency()                       // ✓ succeeds
        await startAllDesignTimeApis()            // ← starts host
          startDesignTimeApi()
            await waitForExtensionBundleReady()   // ← awaits the very
                                                  //   promise above
    finally: resolveInFlight()                    // ← never reached

The post-extract restart awaits the same in-flight promise its own
parent will only resolve once the restart finishes.

Fix: use Node AsyncLocalStorage to scope a 're-entrant' flag to the
async call-tree rooted at downloadExtensionBundle. waitForExtensionBundleReady
checks the store first — if we are inside the download's own scope,
return Promise.resolve() (the bundle is by definition already on disk).
Other concurrent design-time starts (different call-stacks) still
correctly await the in-flight promise.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…le install failed

Even after the delete-first + orphan-kill fix, when extract still fails
(e.g. user declines the terminate-orphan prompt), activation logged
'Azure Logic Apps Standard Runtime Dependencies validation and
installation completed successfully' and then func.exe spawned against
the absent/locked bundle. Result: 'No job functions found' + perpetual
unhealthy storage with no actionable error.

Root cause: the extension-bundle download is fire-and-forget from
main.ts, and neither validateAndInstallBinaries nor startDesignTimeApi
checked its outcome. waitForExtensionBundleReady resolved cleanly even
when the install threw.

Fix:

- bundleFeed.ts: track lastBundleInstallResult ('unknown'|'ok'|'failed')
  + lastBundleInstallError across the lifetime of downloadExtensionBundle.
  Export a new helper, ensureExtensionBundleHealthy(), that awaits the
  in-flight work and throws if the last attempt failed (with a clear,
  actionable message naming orphan func.exe as the likely cause).

- startDesignTimeApi.ts: after the existing wait gate, call
  ensureExtensionBundleHealthy(). Without a healthy bundle, refuse to
  spawn func.exe — the existing error path surfaces the failure to the
  user and avoids the silent 'No job functions found' rabbit hole.

- validateAndInstallBinaries.ts: as the last step of the validation
  flow, await ensureExtensionBundleHealthy(). The bundle is part of
  'runtime dependencies' from the user's perspective; if it failed to
  install, validation should fail loudly rather than logging success.

- Updated the validateAndInstallBinaries.test.ts mock to include the
  new export.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…te actually trips

downloadExtensionBundleCore's outer try/catch swallowed every install error by returning false, defeating the lastBundleInstallResult bookkeeping added in the prior commit: the outer downloadExtensionBundle wrapper saw a successful resolved value, marked the install 'ok', and ensureExtensionBundleHealthy() then passed through. Result: 'Failed to install' was logged, validation immediately logged 'completed successfully', and func.exe spawned against the missing/corrupt bundle.

Re-throw from the catch so the wrapper records 'failed' and downstream callers (validateAndInstallBinaries, startDesignTimeApi) refuse to proceed.

Updated the one test that asserted the swallow-returns-false behavior to expect a rejection + 'failed' install result. Imports getLastBundleInstallResult for the assertion.

All 1084 vs-code-designer unit tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI and others added 3 commits June 19, 2026 16:01
When the extension bundle is corrupted, we delete the existing install

and re-extract in place. On Windows this races with pending-deletion

and with our own draining func.exe — rmSync silently leaves the dir

behind, then mkdirSync at the same path throws EPERM and the install

fails permanently in <1s.

Fix by adding lock-wait wrappers around the rmSync/mkdirSync pair in

extractDependency:

- removeWithLockWait / mkdirWithLockWait poll for up to 30s on

  EPERM/EBUSY/EACCES, logging progress every ~5s.

- After ~5s of persistent failure, prompt the user once to kill orphan

  func.exe processes (existing helper, now reused here).

- stopAllDesignTimeApisAndWait now actually awaits process exit before

  the cleanup begins, instead of fire-and-forget SIGTERM.

Adds 10 unit tests (1094 total now pass). No on-disk layout change —

still extract in place.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After the 30s lock-wait fix made re-extraction succeed, the post-install restart was still firing from inside `downloadAndExtractDependency` while `bundleDownloadScope` was active. That caused `startDesignTimeApi` to short-circuit the new health gate via the AsyncLocalStorage scope check, log a misleading `Waiting for bundle download to complete…` message, and then immediately spawn func.exe before `lastBundleInstallResult` settled to `'ok'` and before the sidecar was written.

- Remove the extensionBundleId branch from binaries.ts `downloadAndExtractDependency` so it no longer triggers a restart inside the active download scope.

- In `downloadExtensionBundle`, fire `startAllDesignTimeApis()` from the `finally` block via a dynamic import (avoids the circular dep with `startDesignTimeApi.ts`) only when the install succeeded and changed disk. Restart runs AFTER `inFlightBundleWork` is cleared and `lastBundleInstallResult` is settled.

- Export `isInsideBundleDownloadScope()` from bundleFeed.ts and tighten the gate in startDesignTimeApi.ts so the `Waiting…` log is suppressed when the call originates from inside the bundle download scope (defensive — the new flow shouldn't reach it).

Tests: 1096 passing (added 2 deferred-restart cases in bundleFeed.test.ts). No new TypeScript errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e validating dependencies

Drops the implicit trust in .bundle-source-md5 as a session-cached source of
truth. ensureExtensionBundleHealthy() now recomputes the on-disk content
hash via the new assertExtensionBundleOnDiskHealthy() helper on every
activation, and synchronously kicks off a repair download when on-disk
state has drifted (e.g. a user manually deleted a subfolder inside the
installed bundle dir). If the repair doesn't restore integrity, the gate
throws so validateAndInstallBinaries reports failure instead of silently
spawning func.exe against a corrupt bundle.

Three short-circuit paths in downloadExtensionBundleCore (env-var pinned
local match, experimental pinned local match, experimental no-pin local
latest) now run verifyLocalBundle first and fall through to a *Repair
download on contentMismatch / sidecarMissing instead of returning false.

Tests: +10 cases in bundleFeed.test.ts covering the new helper, the
repair gate, and the three short-circuit verification paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The p41a prelaunch cleanup was deleting a valid restored NodeJs dependency because CI's extracted POSIX layout has bin/node directly under NodeJs. Probe that layout before falling back to nested node-* folders so strict setup does not force an unnecessary reinstall.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR Validation Error

An error occurred while validating your PR. Please try again later or contact the maintainers.

Error: API request failed with status 500

Strict p41a currently waits for the Workflows bundle sidecar after activation, but activation starts the bundle install in the background and only logs failures. Await the bundle install when LA_E2E_STRICT_DEPENDENCY_VALIDATION is enabled so fixture setup either starts with a healthy sidecar or fails on the real install error before downstream shards can run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR Validation Error

An error occurred while validating your PR. Please try again later or contact the maintainers.

Error: API request failed with status 500

Copilot AI added 6 commits June 22, 2026 18:54
The Workflows bundle path could complete download/extract without writing .bundle-source-md5 when the download result had no source MD5, and sidecar write failures were logged but swallowed. Treat both cases as install failures so strict p41a surfaces the real setup error instead of timing out on a missing sidecar.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid running the extraction progress message through the shell. The Linux shell treats the unquoted attempt parentheses as syntax, which prevented the Workflows extension bundle from extracting during strict p41a validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Include file size when recomputing the Workflows bundle content hash in the E2E repair assertion so it matches the product sidecar contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR Validation Error

An error occurred while validating your PR. Please try again later or contact the maintainers.

Error: API request failed with status 500

Copilot AI added 2 commits June 22, 2026 21:08
Restore the verified Logic Apps extension bundle for p41b while keeping it independent from workspace fixtures. The create-workspace behavior shard only exercises wizard UI, so skip runtime/design-time validation there to avoid blocking the webview on first-run dependency checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor VS Code E2E scenario artifact flags, route bundle repair through the scenario table, and add a bundle hash contract guard without weakening strict setup validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR Validation Error

An error occurred while validating your PR. Please try again later or contact the maintainers.

Error: API request failed with status 500

Copilot AI added 3 commits June 23, 2026 18:46
Use the same raw QuickInput typing path for fallback command searches so p41a fixture setup does not fail when ExTester InputBox is temporarily non-interactable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR Validation Error

An error occurred while validating your PR. Please try again later or contact the maintainers.

Error: API request failed with status 504

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR Validation Error

An error occurred while validating your PR. Please try again later or contact the maintainers.

Error: API request failed with status 504

Copilot AI added 3 commits June 24, 2026 21:48
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Require the Workflows bundle manifest to prove runtime files exist before backfilling missing sidecar metadata, and disable backfill for strict bundle health repair paths so tampered bundles redownload instead of being blessed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR Validation Error

An error occurred while validating your PR. Please try again later or contact the maintainers.

Error: API request failed with status 500

Use the shared robust QuickInput typing helper for create-workspace command selection so CI does not fail when VS Code keeps a hidden pooled quick-input widget during fallback searches.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR Validation Error

An error occurred while validating your PR. Please try again later or contact the maintainers.

Error: API request failed with status 500

Copilot AI added 2 commits June 25, 2026 20:39
Restore the create-workspace command palette typing path that passed before the helper swap, and reopen a fresh command palette before the fallback query so stale no-pick widgets are not reused in p41a setup fixtures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-dependency-downloads

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lambrianmsft lambrianmsft merged commit 4fb4602 into Azure:main Jun 26, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-validated risk:medium Medium risk change with potential impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants