Skip to content

Use codex update for standalone installs#3626

Closed
BearHuddleston wants to merge 9 commits into
pingdotgg:mainfrom
BearHuddleston:codex/codex-standalone-update-command
Closed

Use codex update for standalone installs#3626
BearHuddleston wants to merge 9 commits into
pingdotgg:mainfrom
BearHuddleston:codex/codex-standalone-update-command

Conversation

@BearHuddleston

@BearHuddleston BearHuddleston commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Standalone Codex installs update via codex update instead of npm global installs.

  • Detects the installer's real layout — packages/standalone/releases/<version>/codex, which the current and ~/.local/bin/codex symlinks resolve to — plus current/codex, current/bin/codex, and custom CODEX_HOME roots.
  • Spawns the update with CODEX_HOME pinned to the matched install root, since codex update locates its install via $CODEX_HOME and the maintenance runner spawns with the server env.
  • updateCommand mirrors the spawned action (full path for explicitly configured binaries), so the copyable manual command targets the selected install.
  • updateActionKey (JSON-encoded lockKey/executable/args/env) gates one-click grouping to instances that run the same update action.

Package-manager installs (npm/pnpm/bun/Vite Plus/Homebrew) keep their existing update paths.

Known limitation: instances reaching the same install through different spellings (bare name vs explicit path) don't group for one-click; each still updates individually.

UI Changes

No visual changes.

Validation

  • Server provider tests, web update-notification tests, contracts tests
  • Server/web/contracts typecheck; format and lint on touched files

Checklist

  • Small and focused
  • Explained what changed and why
  • No visual UI changes
  • No animation or interaction changes

Note

Medium Risk
Touches provider update spawning (env, resolved executables) and advisory/UI grouping logic; incorrect path or env handling could update the wrong install or mislead one-click grouping, though behavior is heavily covered by new tests.

Overview
Adds native codex update for standalone Codex installs: detects packages/standalone layouts (including symlinked current and versioned release binaries), pins CODEX_HOME on the spawned update, and disables one-click updates when the configured binary is a pinned release path that codex update would not replace.

Refactors provider maintenance so native updates use the resolved binary path (drops static executable on Claude/OpenCode/Codex), supports optional deriveEnv on update actions, passes env through the maintenance runner spawn, and builds copyable manual commands via shell-safe quoting (POSIX / PowerShell) instead of naive joins. Version advisories gain optional updateActionKey so the UI can tell apart actions that share the same command string.

Update notification UI splits outdated providers into visible candidates, group-safe oneClickCandidates, and dispatchable runnableCandidates; blocks one-click while a same-driver sibling is active; prefers active instances in the sidebar pill; and tightens row/toast lifecycle (no update for settings-only rows, onEmpty when nothing remains, stale errors cleared when targets disappear).

Reviewed by Cursor Bugbot for commit 2d6adf5. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add native one-click update support for Codex standalone installs

  • Adds isCodexStandaloneCommandPath and deriveCodexStandaloneUpdateEnvironment to CodexDriver.ts to detect standalone installs and derive CODEX_HOME for update commands; pinned release binary paths are excluded from one-click eligibility.
  • Refactors resolvePackageManagedProviderMaintenance in providerMaintenance.ts so the resolved command path is used as the executable and derived env vars are passed through, replacing statically configured executables.
  • Adds platform-aware shell quoting (POSIX and PowerShell) via providerMaintenanceCommand.ts; the displayed update command may now be null when safe rendering is not possible.
  • Extends the ServerProviderVersionAdvisory schema with an optional updateActionKey field, used by the frontend to deduplicate and gate one-click update actions across driver instances.
  • Updates ProviderUpdateEnvironmentRows to dispatch updates only for runnableCandidates, hide Update/Retry buttons when no runnable candidates exist, and close the toast via onEmpty when all interacted environments reach a ready state.
  • Risk: updateCommand in advisories may now be null; callers that assumed a non-null string must handle the null case.

Macroscope summarized 2d6adf5.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e018d2c0-0d15-42d2-a4f2-c362d31086ee

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Jul 1, 2026
@BearHuddleston BearHuddleston changed the title [codex] Use codex update for standalone installs Use codex update for standalone installs Jul 1, 2026
@BearHuddleston
BearHuddleston marked this pull request as ready for review July 1, 2026 02:37
Comment thread apps/server/src/provider/Drivers/CodexDriver.ts
Comment thread apps/server/src/provider/Drivers/CodexDriver.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces significant new update functionality for standalone Codex installs with complex shell command generation and UI state management. Three unresolved review comments identify potential bugs in the notification dismissal and toast lifecycle logic that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment thread apps/server/src/provider/Drivers/CodexDriver.ts Outdated
@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Jul 1, 2026
Comment thread apps/server/src/provider/providerMaintenance.ts Outdated

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b49bd08. Configure here.

Comment thread apps/server/src/provider/providerMaintenance.ts Outdated
@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Jul 1, 2026
@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Jul 10, 2026
const relevantGroups = groups.filter((group) =>
relevantEnvironmentIdsRef.current.has(group.environmentId),
);
if (rows.length === 0 && relevantGroups.every((group) => group.connectionState === "ready")) {

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.

🟡 Medium components/ProviderUpdateEnvironmentRows.tsx:480

onEmpty?.() at line 481 can dismiss the wrong notification. After the user interacts, the host keeps the old toast mounted across notificationKey changes and updates notificationKeyRef.current to the latest key. When the old interacted toast's rows empty out and onEmpty fires, the host closes that toast — but dismissPrompt dismisses notificationKeyRef.current, which is now the new key, not the old toast's key. The fresh update set is marked dismissed and its prompt never appears. Consider tracking each toast's own key so onEmpty dismisses only the toast it belongs to, or guard onEmpty so it does not fire when the notification key has changed since the toast was interacted with.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ProviderUpdateEnvironmentRows.tsx around line 480:

`onEmpty?.()` at line 481 can dismiss the wrong notification. After the user interacts, the host keeps the old toast mounted across `notificationKey` changes and updates `notificationKeyRef.current` to the latest key. When the old interacted toast's rows empty out and `onEmpty` fires, the host closes that toast — but `dismissPrompt` dismisses `notificationKeyRef.current`, which is now the *new* key, not the old toast's key. The fresh update set is marked dismissed and its prompt never appears. Consider tracking each toast's own key so `onEmpty` dismisses only the toast it belongs to, or guard `onEmpty` so it does not fire when the notification key has changed since the toast was interacted with.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for flagging this. This path does not invoke dismissPrompt: closeEmptyPrompt calls toastManager.close(active.toastId) directly, while our custom toast.data.onClose callback is invoked only by handleToastDismissClick for an explicit X-button click. A programmatic manager.close() therefore cannot call dismissNotificationKey or dismiss the newer key. The live-key behavior on a manual X click is intentional and separate from the onEmpty path.

Comment thread apps/web/src/components/ProviderUpdateEnvironmentRows.tsx Outdated
Comment on lines +439 to +443
const visibleResult =
(group.candidates.length > 0 && storedResult !== undefined && !hasAttemptedCandidate) ||
(storedResult?.view.phase === "succeeded" && hasUnattemptedCandidate)
? undefined
: storedResult?.view;

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.

🟡 Medium components/ProviderUpdateEnvironmentRows.tsx:439

visibleResult never clears a stored success when group.candidates becomes empty. The condition (group.candidates.length > 0 && ...) || (storedResult?.view.phase === "succeeded" && ...) both short-circuit when there are no candidates, leaving visibleResult set to storedResult?.view. The row stays in "success", and the .filter(...) keeps rows.length nonzero because the status isn't "idle" — so onEmpty never fires and the host toast stays open indefinitely after a successful update. Consider adding a guard so visibleResult returns undefined when group.candidates.length === 0.

Suggested change
const visibleResult =
(group.candidates.length > 0 && storedResult !== undefined && !hasAttemptedCandidate) ||
(storedResult?.view.phase === "succeeded" && hasUnattemptedCandidate)
? undefined
: storedResult?.view;
const visibleResult =
group.candidates.length === 0 ||
(group.candidates.length > 0 && storedResult !== undefined && !hasAttemptedCandidate) ||
(storedResult?.view.phase === "succeeded" && hasUnattemptedCandidate)
? undefined
: storedResult?.view;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ProviderUpdateEnvironmentRows.tsx around lines 439-443:

`visibleResult` never clears a stored success when `group.candidates` becomes empty. The condition `(group.candidates.length > 0 && ...) || (storedResult?.view.phase === "succeeded" && ...)` both short-circuit when there are no candidates, leaving `visibleResult` set to `storedResult?.view`. The row stays in `"success"`, and the `.filter(...)` keeps `rows.length` nonzero because the status isn't `"idle"` — so `onEmpty` never fires and the host toast stays open indefinitely after a successful update. Consider adding a guard so `visibleResult` returns `undefined` when `group.candidates.length === 0`.

@BearHuddleston

Copy link
Copy Markdown
Author

Closing this as stale. I’ll follow up with a smaller, focused replacement.

@BearHuddleston

Copy link
Copy Markdown
Author

Replacement: #4185. This version is intentionally scoped to delegating the normal standalone install update to codex update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants