Skip to content

Extension Version Pinning and Profile Conductor#34

Merged
BenjaminScholtens merged 50 commits into
masterfrom
feature/profile-conductor2
Apr 23, 2026
Merged

Extension Version Pinning and Profile Conductor#34
BenjaminScholtens merged 50 commits into
masterfrom
feature/profile-conductor2

Conversation

@JonahBraun
Copy link
Copy Markdown

Introduces project-scoped extension version pinning and automated profile management to Codex.

Architecture Documentation

Changes

  • Infrastructure & Environment: Stabilized .gitignore and local build scripts.
  • Documentation: Renamed and updated AGENTS.md to reflect new architecture.
  • Extension Bundling: Implemented JSON-driven extension bundling via GitHub Releases with robust download handling.
  • Authoritative Reload: Patched VS Code core to support forced profile selection on window reload.
  • CodexConductor Service: Added workbench contribution for deterministic profile management and pin enforcement.
  • Type Alignment: Centralized and aligned extension metadata types for cross-component compatibility.
  • CLI Pinning: Added Rust CLI commands for managing project extension pins.
  • CLI Sync/Reset: Integrated git-based sync and reset commands for pin lifecycle management.

Add SHOULD_BUILD_REH and SHOULD_BUILD_REH_WEB exports to dev/build.sh
to prevent "unbound variable" errors during local builds. Add .claude/
and fv to .gitignore.
Replace hardcoded extension download logic in get-extensions.sh with a
declarative bundle-extensions.json config. Extensions are downloaded as
pre-built VSIXs from GitHub Releases via `gh release download` and
unpacked into vscode/extensions/ during the build.
A workbench contribution baked into the Codex shell that enforces
project-scoped extension version pins. Reads pin declarations from
project metadata.json (or Frontier's workspaceState), downloads VSIXs
from GitHub Release URLs, installs them into deterministic VS Code
profiles, and switches the extension host.

Includes mid-session pin detection via IStorageService signals, a
3-cycle reload-loop circuit breaker, 14-day automatic profile cleanup,
and a progress notification UX with "Reload Codex When Ready".
Adds `codex pin list/add/remove` subcommands to the Rust CLI for
managing extension version pins in project metadata.json. The `add`
command downloads a remote VSIX, extracts the extension ID and version
from its package.json, and writes the pin entry.

The patch registers `pin` as a native CLI command in argv.ts with
Node-to-Rust hand-off, adds PinningError to the error types, and
refactors the macOS "Install Shell Command" to create both a `codex`
and `codex-cli` symlink (the latter pointing directly to codex-tunnel
for direct Rust CLI access without the Node wrapper).

pin.rs is delivered via source overlay; the patch modifies existing
VS Code files (args, argv, nativeHostMainService). The patch is built
on the baseline of binary-name.patch which it depends on.
Move development instructions to AGENTS.md (readable by both humans and
AI agents). CLAUDE.md becomes a symlink to AGENTS.md for backward
compatibility.
…ex components

Remove redundant tutorial-style content and stale merge strategy details.
Add build pipeline diagram, overlay vs patch guidance, patch dependency
table, and documentation for CodexConductor, CLI pin commands, and
extension bundling.
…hardcoded "code"

The Rust CLI's version_manager.rs had five hardcoded references to "code" as the
editor binary name. This caused codex-tunnel commands (e.g. pin) to fail with
"No such file or directory" when looking for bin/code instead of bin/codex.

## Changes
- Update DESKTOP_CLI_RELATIVE_PATH to use concatcp! with APPLICATION_NAME
- Update detect_installed_program /Applications/ fast path
- Update detect_installed_program system_profiler fallback path
NativeExtensionManagementService.downloadVsix() intercepts install()
calls in the renderer and downloads via browser fetch(), which fails
for GitHub release URLs due to CORS on the 302 redirect. Route the
install call through the shared process IPC channel directly, where
Node.js networking handles redirects without CORS restrictions.

Also adds retry logic with backoff, profile cleanup on failure, and
richer error reporting with Copy Error Report action.
…ement

Resolved a reliability issue where extension version pin enforcement would enter
an infinite reload loop, especially when running in extension development mode
or when custom editors vetoed the extension host restart.

Core Changes:
- Implement "Authoritative Reload": Patched VS Code core to allow the reload()
  IPC command to accept an explicit forceProfile name.
- Patch windowImpl.ts to respect the passed profile name and explicitly
  revive workspace URIs during lookup to bypass Main process stale-cache issues.
- Patch windowsMainService.ts to allow profile-workspace associations to be
  persisted even when launched with --extensionDevelopmentPath.
- Update CodexConductor to use the authoritative reload signal and explicitly
  call resetWorkspaces() before switching to prevent lookup conflicts.

Build System:
- Fix build_cli.sh to use mkdir -p when preparing OpenSSL to prevent
  spurious build failures.

Documentation:
- Updated AGENTS.md with details on the authoritative reload and robustness
  features.
The forceProfile authoritative reload path looks up the profile from
Main process memory, not disk. The timeout was unnecessary since the
profile association is already in-memory when reload fires.
Adds a Command Palette command (Codex: Manage Extension Pins) for in-editor
pin management. Supports viewing required/pinned extensions, adding pins from
VSIX URLs or GitHub release pages, removing pins, and syncing via Frontier.

Also adds GitHub release page URL resolution to the Rust CLI pin command, and
fixes metadata.json serialization to use 4-space indent matching codex-editor.

## Changes
- New codexPinManager.ts workbench contribution with QuickPick hub UI
- CLI resolve_vsix_url() resolves release page URLs to VSIX download URLs
- CLI write_metadata uses 4-space indent (matches codex-editor convention)
…nvalid metadata

Previously, calling initialize() multiple times leaked storage listeners, non-FOLDER
workspaces left users stranded on conductor profiles, and missing or invalid
metadata.json caused early returns that skipped revertIfPatchBuild().
…profiles

The CONDUCTOR_PROFILE_PATTERN regex didn't match pre-release version suffixes
(e.g. codex-editor-v0.24.0-pr816-1148908f), so revertIfPatchBuild() silently
skipped revert when opening a project without pins. Setting the 'repo-pinned'
icon on creation provides a reliable, self-describing marker on the profile
itself.
After removing a pin, metadata.json retains an empty `pinnedExtensions: {}`. `readPinsSnapshot()` treated this as truthy, returning `"{}"` instead of `undefined`, causing `resolveProfileName()` to error on `undefined.includes('.')`.
`setProfileForWorkspace` internally updates `currentProfile` even when the extension host vetos the switch. The post-call ID check then incorrectly reports "already on target" and skips the authoritative reload, causing duplicate extension registrations and a blank sidebar. Capture the profile ID before the call instead.
… all boundaries

Consolidates duplicate PinnedExtensionEntry, PinnedExtensions, RequiredExtensions, and ProjectMetadata declarations into a shared module. Adds parsePinnedExtensions() which validates entry shape (string version and url) and drops malformed entries, replacing all raw JSON.parse casts.
VS Code stores an extension's entire workspaceState as a single JSON blob
under the extension ID key. The conductor was reading a dotted subkey
(frontier-rnd.frontier-authentication.remotePinnedExtensions) that never
existed. Read the blob key and extract the remotePinnedExtensions field
from within it. This fixes the entire storage-based flow: initial
enforcement from remote pins, mid-session detection, and sync deadlock
resolution.
…entication

Change RequiredExtensions from Record<string, string> to an interface with
specific codexEditor and frontierAuthentication keys. Update pin manager hub
to use known keys instead of generic Object.keys() indexing.
The codex-editor extension activates before frontier-authentication
after conductor profile switches (onView vs onStartupFinished), so
frontier commands aren't available yet. This command lets codex-editor
read the resolved pins directly from IStorageService without depending
on frontier.
Introduces a dedicated Service API for extension pinning, moving state ownership from Frontier into the Conductor.

- Adds storage keys for adminPinnedExtensions, remotePinnedExtensions, and syncCompletedAt in the Conductor namespace.
- Registers IPC commands (setAdminPinIntent, setRemotePins, getPinMismatches, etc.) to provide a clean API for other extensions.
- Centralizes version mismatch detection using internal shell services for better reliability.
- Updates codexPinManager UI to use the new Conductor-owned state commands.
Ensures reliable mid-session state synchronization and correct notification triggers.

- Adds a storage listener for syncCompletedAt to trigger state re-evaluation after sync lands metadata on disk (fixes Scenario 3).
- Explicitly calls storageService.remove() when pins are cleared to ensure listeners fire reliably.
- Removes development-era storage key annotations and cleans up internal state handling.
Exposes a new `codex.conductor.hasAdminPinIntent` command that returns
true when `adminPinnedExtensions` is set in workspace storage. Extensions
(codex-editor) can query this to suppress auto-sync while the admin is
sanity-testing a freshly pinned extension version.
Codex* was matching src/.../codexConductor on macOS's case-insensitive
filesystem, silently ignoring the entire conductor source tree. Fixed by
changing it to /Codex* (root-anchored).

Also:
- Remove redundant vscode/ (already covered by the explicit rule)
- Add *.vscdb to ignore VS Code workspace state databases
- Group rules logically with comments
… associations mid-session

Three fixes:
- readPinsSnapshot() now canonicalizes both top-level key order and nested
  entry field order, preventing false-positive pin change detection from
  JSON property ordering differences across parse/write cycles.
- Mid-session reload paths (pin change, pin removal, install completion)
  now use switchProfileAndReload() instead of bare hostService.reload(),
  which persists the workspace-profile association via setProfileForWorkspace()
  before reloading. Previously, forceProfile handled the immediate reload
  but the association was never persisted, causing potential extra reload
  cycles on subsequent reopens.
- The "Reload Codex When Ready" auto-reload path now awaits
  switchProfileAndReload() so rejections are caught by the surrounding
  try/catch and surfaced via the error notification UX.
…wline to metadata writes

- Remove get_vsix_metadata_smart() which made real HEAD + Range requests
  on every `pin add` then unconditionally fell through to full download,
  wasting two round-trips. Replaced with a TODO documenting the intended
  optimization.
- write_metadata() now appends a trailing newline to match the TypeScript
  PinManager's JSON output, avoiding noisy git diffs when both paths
  touch the same metadata.json.
If Codex quits mid-VSIX-install, the profile exists on disk but has no
extensions. Previously, the conductor trusted name match as proof of
completeness, causing a stuck state where codex-editor yields forever.
Now calls getInstalled(type, profileLocation) to verify pinned extensions
are present before skipping download. Incomplete profiles are repaired
in-place.
Updates the pre-existing RequiredExtensions type definition to ensure cross-boundary compatibility and correct parsing between the codex shell and upstream repositories like codex-editor and frontier-authentication. This guarantees structural type safety across boundaries.
Modifies the existing Rust CLI build process and adds the base pin add, remove, and list subcommands. This provides a robust terminal interface for admins to locally manage pinnedExtensions programmatically. Includes necessary semver validation, trailing newline formatting for metadata writes, and sets up the codex-cli macOS symlink.
Extends the new CLI pinning module to add sync and reset commands. This is a distinct functional addition to the CLI that allows administrators to safely stage, commit, or discard their local pin changes using git-based dirty checks directly from the command line interface.
switchProfileAndReload called resetWorkspaces() as a pre-cleanup, but that
method clears workspaces on every profile globally — so switching profiles
in one project window erased other open projects' associations.

The pre-cleanup was also redundant: setProfileForWorkspace calls
updateProfile, which cascades and removes the workspace from every other
profile automatically (common/userDataProfile.ts:376-381).
The payload handed to lifecycleMainService.reload() is a NativeParsedArgs
and every key we set ({ _, 'disable-extensions', 'profile' }) is already
declared on that interface (argv.ts:42, 93, 138). The cast was bypassing
type safety for no reason.
The authoritative-reload patch removed the !extensionDevelopmentPath guard
around setProfileForWorkspace in doOpenInBrowserWindow, but the guard
removal wasn't actually needed for the conductor flow:

- Reloads route through windowImpl.ts (forceProfile by name lookup), not
  doOpenInBrowserWindow.
- The conductor persists associations via the renderer IPC, which hits the
  unconditional main-process setProfileForWorkspace — no EDH gate there.
- Subsequent EDH opens read the persisted association via
  getProfileForWorkspace (guard only blocks writes, not reads).

Removing the hunk restores upstream behavior: dev launches with
--profile scratch --extensionDevelopmentPath don't contaminate the
workspace-profile association for later non-EDH opens.
@JonahBraun JonahBraun force-pushed the feature/profile-conductor2 branch from e893df5 to 3590fd5 Compare April 22, 2026 22:12
@github-actions
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown

1 similar comment
@github-actions
Copy link
Copy Markdown

- stable-linux: add GITHUB_TOKEN to compile Build step env so
  get-extensions.sh's `gh release download` can authenticate.
- get-extensions.sh / build.sh: execute get-extensions.sh instead of
  sourcing it, so its `set -u` doesn't leak into build.sh and trip
  the CI_BUILD unbound-variable check on the Windows compile job.
- dev/build.sh: make SHOULD_BUILD_REH / SHOULD_BUILD_REH_WEB
  overridable via env (${VAR:-no}) so the Dockerfile's inline
  `SHOULD_BUILD_REH=yes SHOULD_BUILD_REH_WEB=yes ./dev/build.sh`
  actually takes effect.
- Dockerfile: override SHOULD_BUILD_REH=yes SHOULD_BUILD_REH_WEB=yes
  on `./dev/build.sh`, since the runtime stage needs
  vscode-reh-web-linux-x64/ which dev/build.sh now skips by default.
- pr-build: pass --repo to gh pr comment in notify-failure so the
  build-failed comment and thumbs-down reaction post without
  requiring an actions/checkout in that job.
@JonahBraun JonahBraun force-pushed the feature/profile-conductor2 branch from 7ce7a52 to 4b48379 Compare April 23, 2026 04:09
@JonahBraun
Copy link
Copy Markdown
Author

/build

@github-actions
Copy link
Copy Markdown

- compile-windows: cross-compile on ubuntu-22.04 with OS_NAME=windows,
  upload vscode.tar.gz artifact.
- build-windows: unpack on windows-2022, run package.sh, sign app
  binaries (.exe/.dll) and installers (.exe/.msi) in two passes with
  SSL.com eSigner so binaries inside the installer are also signed.
- release: include windows-x64 .exe and .msi in the PR prerelease,
  add build-windows to release needs.
- notify-failure: watch compile-windows and build-windows so the
  thumbs-down / Build failed comment fires if either fails.

Version format: RELEASE_VERSION must satisfy two constraints —
get_repo.sh's ^([0-9]+\.[0-9]+\.[0-5])[0-9]+$ regex and Inno Setup's
VersionInfoVersion (each dotted component an unsigned 16-bit int).
Use MS_TAG + hour-of-year TIME_PATCH; carry PR number and short hash
only in the release title / PR comment.

Windows env: set OS_NAME=windows so build.sh and build_cli.sh take
the Windows branches; CI_BUILD=yes so build/windows/package.sh
doesn't exit early; SHOULD_BUILD_REH=no on both jobs to match.
@JonahBraun JonahBraun force-pushed the feature/profile-conductor2 branch from 4b48379 to 8c66b1c Compare April 23, 2026 06:08
@github-actions
Copy link
Copy Markdown

JonahBraun and others added 6 commits April 23, 2026 10:11
- Override nameShort/nameLong/darwinBundleIdentifier in product.json
  before the mac build so the .app is "Codex Beta.app" instead of
  "Codex.app". prepare_vscode.sh hardcodes these regardless of
  APP_NAME; the root product.json merge is the override point.
- Same override on the Windows compile job, plus win32DirName /
  win32NameVersion / win32ShellNameShort so Start Menu & installer
  show "Codex Beta".
- Release tag is now "\${VERSION}-pr\${PR_NUMBER}-\${SHORT_HASH}" so
  repeat builds of the same PR don't collide. App-internal version
  strings stay digits-only (Windows installer constraint).
Two-part change so that pinned extensions on conductor-managed profiles
aren't silently updated out from under us:

- patches/feat-codex-allow-profile-extension-updates.patch: drop
  `scope: ConfigurationScope.APPLICATION` from `extensions.autoUpdate`
  and `extensions.autoCheckUpdates`. Upstream locks these to the
  application level, so setting them in a profile has no effect. With
  APPLICATION removed the settings default to WINDOW scope and become
  per-profile overridable. Side effect: all users (not just conductor
  ones) gain per-profile control over these two keys.

- CodexConductor.seedProfileSettings: writes
  extensions.auto{Update,CheckUpdates}=false into a conductor profile's
  settings.json. Called before every profile switch, and as backfill on
  startup when already sitting on a conductor profile (covers profiles
  created before this change). Uses jsonEdit.setProperty + applyEdits
  so any user-authored comments / formatting in the file survive.
Replaces the standalone `extension-sideloader` with a built-in Workbench Contribution.
This new architecture installs extensions directly via the internal `IWorkbenchExtensionManagementService`
during the `AfterRestored` phase, ensuring global extensions are ready before the Extension Host populates.
Supports both Open VSX gallery IDs and direct VSIX URLs, with version-aware reinstallation for VSIXs.
Installs are routed to the global location to ensure visibility across all VS Code profiles.
Improves the reliability of the build pipeline across local and CI environments.
Updates `prepare_assets.sh` to safely execute locally without strictly requiring CI environment variables.
Fixes TypeScript compilation issues (e.g., casting through unknown, guarding empty bundle arrays)
and ensures DMG creation doesn't fail the build pipeline if `npx` encounters non-critical errors.
Introduces automated and manual workflows for building pre-release artifacts from PRs.
Adds the `create-pr-release` script to generate VSIXs/builds with 'Codex Beta' branding for testing.
Configures `.github/workflows/pr-build.yml` to be triggered via GitHub comments and utilizes
`workflow_call` for reusable CI steps, standardizing the build environment variables.
…ration

Updates the extension bundling lists for the new sideloader architecture.
Removes `extension-sideloader` from `bundle-extensions.json`.
Populates `codexSideloadExtensions` in `product.json` with the required global extensions,
including switching `codex-editor` and `frontier-authentication` to specific pre-release VSIXs
for beta testing. Bumps extension versions and fixes broken extension links.
@github-actions
Copy link
Copy Markdown

@JonahBraun JonahBraun force-pushed the feature/profile-conductor2 branch from b0ef2a8 to ee0dffc Compare April 23, 2026 18:39
@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown

@BenjaminScholtens BenjaminScholtens left a comment

Choose a reason for hiding this comment

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

works good

@BenjaminScholtens BenjaminScholtens merged commit 7eaef8c into master Apr 23, 2026
23 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.

3 participants