Skip to content

Rework extension management for pinned profiles#37

Merged
LeviXIII merged 5 commits into
masterfrom
fix/conductor-core-extension-backfill2
May 12, 2026
Merged

Rework extension management for pinned profiles#37
LeviXIII merged 5 commits into
masterfrom
fix/conductor-core-extension-backfill2

Conversation

@JonahBraun
Copy link
Copy Markdown

@JonahBraun JonahBraun commented May 6, 2026

Extensions management rework to address three things with pinned extension profiles:

  • fix bug where some users could not open a pinned profile ("Cannot read the extension from …")
  • allow only one pinned extension; other required extensions install from the latest stable on the marketplace
  • progress notification while extensions are installing

Changes

  • Pinned profiles get the full core extension set, not just the pin (5fd88fa). A project's metadata.json only needs to pin one extension; the others come from the latest stable on the marketplace. The conductor reads codexSideloadExtensions from product.json (no more hard-coded constants) and backfillCoreExtensions installs every non-pinned core extension into the target profile (gallery for string entries, VSIX channel for object entries) after installPinnedExtensions. validateProfileExtensions and enforcePins flag a profile missing any core extension as incomplete, and installPinnedExtensions early-skips the VSIX download when the target profile already has the pinned version. switchProfileAndReload(profile, forceReload?) adds an explicit reload param for the repair-on-current-profile path.
  • Sideload types unified into codexTypes.ts (7562240). SideloadEntry, SideloadVsixEntry, and parseSideloadEntries moved out of codexSideloader.ts so the conductor can reuse them.
  • patches/fix-conductor-extension-read.patch (new in 7bf3034). Passes includeInvalid: true to scanUserExtensions in the profile branch of ExtensionsScanner.scanLocalExtension, mirroring the non-profile branch. Without it, post-install verification of a VSIX with a pre-release version (0.24.1-pr123-abc1234) would throw "Cannot read the extension" even though the install succeeded on disk.
  • Stop pinned VSIX installs from inheriting app-scope from sideloads (3791314). This was the original "Cannot read the extension" bug. The sideloader used to mark its installs isApplicationScoped: true, which bridged them into conductor profiles via VS Code's cross-profile scanner. Once the conductor backfills explicitly (above), that bridge is unnecessary and actively harmful: the upstream install task at extensionManagementService.ts:1047 inherits isApplicationScoped from the bridged copy, so the conductor's pin would inherit the flag, get filtered out of its own profile by the scanner, and verification would fail. Fix has three parts:
    • Sideloader passes isApplicationScoped: false explicitly on every install, and a new migrateLegacyAppScope() runs in every window (including conductor profiles) to clear the flag from existing data via updateMetadata. Documented in a top-of-file comment.
    • Conductor passes isApplicationScoped: false explicitly in installPinnedExtensions and both branches of backfillCoreExtensions.
    • New patches/fix-extension-scope-inheritance.patch changes || to ?? on extensionManagementService.ts:1047 so the explicit false actually wins over an inherited true.
  • Progress notification during startup pin install (9c995cd). enforcePins previously installed pinned VSIXs silently behind the splash screen. Mirrors the toast styling used by checkForPinChanges so first-open and repair flows surface the same indeterminate "Installing pinned extension…" notification while the conductor downloads.
  • AGENTS.md patch dependency table — adds the two new patches.

Test plan

  • Patches apply cleanly./dev/update_patches.sh finishes without rejecting fix-conductor-extension-read.patch or fix-extension-scope-inheritance.patch.
  • Build succeeds./dev/build.sh -s produces VSCode-darwin-arm64/Codex.app without errors.
  • Pre-release VSIX install — pin codex-editor to a -pr build (e.g. 0.24.1-pr123-abc1234). Open a project with that pin. Install completes without "Cannot read the extension"; the conductor switches to the new profile.
  • App-scope migration on legacy state — start from a state where the Default-profile sideloads have isApplicationScoped: true (any build before this PR). Open Codex. Sideloader logs [CodexSideloader] Migrated legacy app-scope on "<id>" for each affected extension. Default/extensions.json no longer has isApplicationScoped:true on those entries.
  • Pinned VSIX install on already-broken state — same starting state as above, immediately open a pinned project. The conductor's pinned-VSIX install completes without "Cannot read the extension" and the right pin version actually runs after reload (not the sideloaded version).
  • Fresh-profile backfill (golden path) — pin codex-editor only. Open a fresh project. After the conductor reload, the new profile has all four core extensions installed.
  • Repair-on-current-profile reload — using a build without this branch, open a pinned project to create an incomplete conductor profile. Quit. Build the branch and reopen. Conductor logs "repairing", backfills the missing extensions, and automatically reloads.
  • Skip-pinned-from-backfill — pin only codex-editor. Open project. Conductor backfills the other three but does not duplicate-install codex-editor.
  • Early-skip on already-installed pin — delete one core extension from a profile's extensions folder, then reopen. Conductor logs "already installed in profile" for the unaffected pinned ones and only the missing one downloads.
  • Mid-session pin change — open an unpinned project. Add a pin via codex pin sync from another window and trigger Frontier sync. Confirm: "Installing pinned extension…" → completion → "Reload Codex" → after click, new profile active with all extensions.
  • Unpin (no regression) — open a pinned project. Remove the pin, sync. After reload, lands back on Default with sideloader-installed extensions.
  • Network failure during backfill — block open-vsx.org and open a pinned project. Error notification with "Open in Default Profile" / "Copy Error Report"; half-built profile is removed.
  • Progress notification visible at startup — open a fresh pinned project. While the conductor downloads, the "Installing pinned extension…" toast is visible (not just behind the splash screen).
  • No-pin project unaffected — open a project without pinnedExtensions. No profile created; sideloader handles core extensions in Default.

JonahBraun added 5 commits May 5, 2026 18:38
…profiles

This ensures that pinned extensions with prerelease tags or other minor manifest
warnings can be successfully verified during profile installation.

By default, the profile scanner filters out extensions marked as invalid. In the
context of the Conductor, which often installs custom VSIX builds with unstable
version strings, this filtering causes the verification step to fail with a
'Cannot read the extension' error even if the installation succeeded on disk.
This commit mirrors the behavior of non-profile installations by explicitly
including invalid extensions during the verification scan.
Consolidates the definition of 'core' extensions and their parsing logic into
a shared location.

Previously, the Sideloader and Conductor had duplicated (and in the Conductor's
case, hard-coded) definitions for core extensions. By moving the SideloadEntry
types and the parseSideloadEntries utility to codexTypes.ts, we establish a
single source of truth for configuration and prepare for the Conductor to
dynamically derive its core extension list from product.json.
Refactors the Conductor to dynamically derive its core extension list from
product.json and correctly honor VSIX-pinned sideloads in custom profiles.

This commit addresses several architectural issues:
1. P2 Hard-coded Duplication: Removed CORE_EXTENSION_IDS constant. Core
   extensions are now read from productService.codexSideloadExtensions.
2. P1 VSIX Divergence: The backfillCoreExtensions helper now handles both
   gallery and VSIX-based installations. If a beta build specifies a VSIX for
   a core extension, the Conductor will install that exact version into the
   pinned profile instead of falling back to the public gallery.
3. P1 Pin Conflict Logic: enforcePins and validateProfileExtensions now
   explicitly skip core-version checks if the extension is already pinned
   to a specific version in the project metadata. This ensures project-specific
   pins take precedence without triggering repair/reload loops.
…rom sideloads

Pinned VSIX installs were failing post-install with "Cannot read the
extension from /Users/j/.codex/extensions/...". Three independent
decisions interacted to cause this:

  1. d9f6ee6 marked sideloaded default-profile extensions as
     `isApplicationScoped: true` to make them visible inside conductor
     profiles via VS Code's cross-profile bridge.
  2. 5fd88fa added `backfillCoreExtensions`, which copies sideloaded
     extensions into each conductor profile explicitly. This made (1)
     redundant.
  3. extensionManagementService.ts:1047 inherits `isApplicationScoped`
     from any existing extension via `||`. When the conductor installs
     a pinned VSIX into a custom profile, the existingExtension lookup
     finds the bridged Default-profile sideload (app-scoped) and the
     new install inherits the flag. The post-install profile scan then
     filters out app-scoped entries and looks for them in the Default
     profile — where the pinned version doesn't exist — so the find
     fails and "Cannot read the extension" is thrown. Even if it
     succeeded, dedup prefers app-scoped, so the conductor's pin would
     be silently shadowed by the sideload at runtime.

Fix:
  - Sideloader: pass `isApplicationScoped: false` explicitly on every
    install. Add `migrateLegacyAppScope()` that runs in every window
    (not just default-profile) and uses `updateMetadata` to clear the
    flag on legacy data. Document the rationale in a top-of-file comment.
  - Conductor: pass `isApplicationScoped: false` explicitly in
    `installPinnedExtensions` and both branches of `backfillCoreExtensions`.
  - New patch `fix-extension-scope-inheritance.patch`: change \`||\` to
    \`??\` on extensionManagementService.ts:1047 so an explicit
    \`isApplicationScoped: false\` from options actually wins over an
    inherited \`true\`.
  - Update AGENTS.md patch-dependency table.

Verified: legacy state migrates on next launch (sideloader logs
"Migrated legacy app-scope on …" for each affected extension), the
conductor's pinned VSIX install completes without the
"Cannot read the extension" error, and the conductor profile ends up
with the correct pin versions actually installed and runnable.
enforcePins() previously installed pinned VSIXs silently behind the
splash screen. Mirror the toast styling used by checkForPinChanges()
so first-open and repair flows surface the same "Installing pinned
extension…" indeterminate progress notification while the conductor
downloads and installs.
@JonahBraun JonahBraun changed the title Backfill core extensions into conductor-managed profiles Rework extension management for pinned profiles May 7, 2026
@JonahBraun
Copy link
Copy Markdown
Author

/build

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

@LeviXIII LeviXIII merged commit c4fc1a7 into master May 12, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants