Skip to content

feat(cli): Codex-style auto-update prompt and e2b update command - #2007

Draft
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1792518652-cli-auto-updater
Draft

devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1792518652-cli-auto-updater

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Replaces the passive simple-update-notifier box with a Codex-CLI-style updater (src/utils/updater.ts) that knows how the CLI was installed and can run the upgrade itself.

  • Install detection: detectInstallMethod(realpath(argv[1])) checks the path in this order:
    • /Cellar/e2b/ → brew
    • /_npx/ → other
    • /.bun/install/global/ → bun
    • /pnpm/global/ → pnpm
    • /lib/node_modules/@e2b/cli/ or Windows %APPDATA%\npm\node_modules\@e2b\cli → npm
    • anything else (local devDependency, repo checkout) → other, which only gets a notice
  • Upgrade commands:
    • npm: npm install -g @e2b/cli@latest
    • pnpm: pnpm add -g @e2b/cli@latest
    • bun: bun add -g @e2b/cli@latest
    • brew: brew upgrade e2b
  • Latest version: comes from the npm registry, or for brew installs from formulae.brew.sh/api/formula/e2b.json, because the formula lags npm (2.22.0 vs 2.22.2 today).
  • Cache: ~/.e2b/update-check.json stores { lastCheckedAt, latestVersion, dismissedVersion? } and is refreshed in the background every 20h. Startup reads only the cache and never waits on the network, so a new version shows up on the next run (same as Codex).
  • Startup prompt: shown before the command runs, only when stdin and stdout are TTYs. It is skipped for --version, --help, help, update, under CI, and when NO_UPDATE_NOTIFIER is set.
    ? Update available! 2.22.3 -> 2.23.0
    ❯ Update now (runs `npm install -g @e2b/cli@latest`)
      Skip
      Skip until next version
    
    "Update now" runs the upgrade and exits with "Run your command again". The old code is already loaded, so the original command is not run.
  • Notice: when the prompt isn't shown (non-interactive, or install method other), a one-line notice goes to stderr after the command, only if stderr is a TTY.
  • e2b update: always fetches the latest version fresh and runs the detected upgrade. If it can't detect the install method, it prints the npm command as an example and exits 1.
  • Dependency: simple-update-notifier is removed.

Usage:

e2b update                     # upgrade via the detected package manager
NO_UPDATE_NOTIFIER=1 e2b ...   # opt out of checks and prompts

Linear: SDK-396

Link to Devin session: https://app.devin.ai/sessions/1789eb3090a04ff9a5c2987719899361
Open in Devin Desktop: https://app.devin.ai/desktop/session/1789eb3090a04ff9a5c2987719899361?variant=devin
Requested by: @mishushakov

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@cla-bot cla-bot Bot added the cla-signed label Oct 20, 2026
@changeset-bot

changeset-bot Bot commented Oct 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f28b382

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@e2b/cli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from 4a108ea. Download artifacts from this workflow run.

JS SDK (e2b@2.58.1-devin-1792518652-cli-auto-updater.0, with @e2b/dockerfile-utils@0.1.2-devin-1792518652-cli-auto-updater.0):

npm install ./e2b-dockerfile-utils-0.1.2-devin-1792518652-cli-auto-updater.0.tgz ./e2b-2.58.1-devin-1792518652-cli-auto-updater.0.tgz

CLI (@e2b/cli@2.22.4-devin-1792518652-cli-auto-updater.0):

npm install ./e2b-cli-2.22.4-devin-1792518652-cli-auto-updater.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.9.1-devin-1792518652-cli-auto-updater.0):

npm install ./e2b-code-interpreter-2.9.1-devin-1792518652-cli-auto-updater.0.tgz

Desktop JS SDK (@e2b/desktop@2.5.1-devin-1792518652-cli-auto-updater.0):

npm install ./e2b-desktop-2.5.1-devin-1792518652-cli-auto-updater.0.tgz

Python SDK (e2b==2.58.0+devin.1792518652.cli.auto.updater, with e2b-dockerfile-utils==0.1.1+devin.1792518652.cli.auto.updater):

pip install ./e2b_dockerfile_utils-0.1.1+devin.1792518652.cli.auto.updater-py3-none-any.whl ./e2b-2.58.0+devin.1792518652.cli.auto.updater-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.11.2+devin.1792518652.cli.auto.updater):

pip install ./e2b_code_interpreter-2.11.2+devin.1792518652.cli.auto.updater-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.7.1+devin.1792518652.cli.auto.updater):

pip install ./e2b_desktop-2.7.1+devin.1792518652.cli.auto.updater-py3-none-any.whl

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TASTE.md compliance review (CLI-only PR, so the SDK-surface rules T-1–T-46 and T-54–T-74 mostly don't apply).

Checked: T-12 (acronym casing), T-43 (shell composition: spawn uses fixed argv arrays, nothing is interpolated, OK), T-47 (named default constants), T-49/T-50 (env-var config and empty-means-unset), T-56 (version imported from package.json, OK), T-62/T-63 (actionable error messages).

Violations: 3 rules, 4 inline comments

  • T-50: NO_UPDATE_NOTIFIER / CI are checked with !== undefined, so an empty value counts as set. Flagged in both index.ts and updater.ts. This also regresses from the old truthiness check.
  • T-47: the 5000 ms fetch timeout is a magic number in fetchLatestVersion.
  • T-62: e2b update doesn't tell the user what to do when the version fetch fails.

Not tied to a line / not counted:

  • T-49: NO_UPDATE_NOTIFIER isn't E2B_-prefixed. It's the existing cross-tool opt-out shared with update-notifier, and this PR keeps it, so I'm not counting it.
  • T-12: the new import * as packageJSON in commands/update.ts would be packageJson under the rule, but it copies the existing name in src/index.ts. Rename both together if at all.

Comment thread packages/cli/src/index.ts
Comment on lines +61 to +62
process.env.NO_UPDATE_NOTIFIER !== undefined ||
process.env.CI !== undefined ||

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

T-50 — an env var set to the empty string means unset. !== undefined treats an exported-but-empty NO_UPDATE_NOTIFIER= (or CI=) as set, so a blank value silently disables the updater. The code this replaces used truthiness (process.env.NO_UPDATE_NOTIFIER ? …), so this is also a behavior change for anyone with an empty var in their shell profile.

Suggested change
process.env.NO_UPDATE_NOTIFIER !== undefined ||
process.env.CI !== undefined ||
!!process.env.NO_UPDATE_NOTIFIER ||
!!process.env.CI ||

Comment on lines +230 to +231
env.CI !== undefined ||
env.NO_UPDATE_NOTIFIER !== undefined ||

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

T-50 — same issue here: an empty CI= / NO_UPDATE_NOTIFIER= should count as unset, but !== undefined makes it suppress the prompt. Check truthiness so this matches the index.ts gate. (This also repeats isUpdaterDisabled() from index.ts; keeping one shared check would stop the two from drifting apart.)

Suggested change
env.CI !== undefined ||
env.NO_UPDATE_NOTIFIER !== undefined ||
!!env.CI ||
!!env.NO_UPDATE_NOTIFIER ||

fetchFn: typeof fetch = fetch
): Promise<string> {
const url = method === 'brew' ? BREW_FORMULA_URL : NPM_REGISTRY_URL
const response = await fetchFn(url, { signal: AbortSignal.timeout(5000) })

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

T-47 — defaults go in named module constants, not magic numbers at the call site. UPDATE_CHECK_INTERVAL_MS already does this; the 5 s request timeout should too. Declare const UPDATE_CHECK_REQUEST_TIMEOUT_MS = 5_000 next to UPDATE_CHECK_INTERVAL_MS and use it here:

Suggested change
const response = await fetchFn(url, { signal: AbortSignal.timeout(5000) })
const response = await fetchFn(url, {
signal: AbortSignal.timeout(UPDATE_CHECK_REQUEST_TIMEOUT_MS),
})

try {
latestVersion = await fetchLatestVersion(method)
} catch (error) {
console.error('Failed to fetch the latest E2B CLI version:', error)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

T-62 — error messages should be actionable and say what to do, not just what failed. This prints the raw error (including its stack) and leaves the user stuck. The other failure paths in this command already name the manual fallback; do the same here:

Suggested change
console.error('Failed to fetch the latest E2B CLI version:', error)
console.error(
`Failed to fetch the latest E2B CLI version: ${error instanceof Error ? error.message : String(error)}. Check your network connection, or update manually with \`${getUpdateCommandDisplay(method) ?? 'npm install -g @e2b/cli@latest'}\`.`
)

@linear-code

linear-code Bot commented Oct 21, 2026

Copy link
Copy Markdown

SDK-396

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant