Skip to content

feat(cli): authenticate with the refreshed access token, drop the API key - #2103

Merged
mishushakov merged 6 commits into
mainfrom
mishushakov/cli-access-token-auth
Nov 12, 2026
Merged

mishushakov merged 6 commits into
mainfrom
mishushakov/cli-access-token-auth

Conversation

@mishushakov

@mishushakov mishushakov commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Summary

The CLI now authenticates every request with the login's OAuth access token instead of the project API key it used to store in ~/.e2b/config.json. It refreshes the token automatically, including during long-running commands and across concurrent e2b processes. E2B_API_KEY still works and wins, for CI/CD. Needs #2102 (merged 2026-11-10); docs in e2b-dev/docs#636.

Bottom of stack #2115: #2103 → #2104 → #2105 → #2124 → #2125; each PR merges into the one below.

Changes

1. Login URL. e2b auth login opens https://e2b.dev/auth/cli (https://$E2B_DOMAIN/auth/cli when set). DOCS_BASE and the E2B_DOCS_BASE override are removed. Both the old e2b.dev/docs/api/cli and the new URL already 308 to console.e2b.dev/auth/cli, so the browser flow itself is unchanged.

2. Automatic refresh. ensureAuth() replaces ensureAPIKey() everywhere:

const auth = await ensureAuth() // { apiKey } from E2B_API_KEY, else:
// { apiHeaders: { Authorization: 'Bearer <access token>', 'X-Team-ID': <project> } }
await Sandbox.list({ ...auth })
  • Tokens are refreshed when they expire within a minute; they live one hour.
  • Refresh tokens rotate, and the old one is rejected once used. Before, two commands refreshing at the same time could get invalid_grant, which deletes the config and signs the user out. Refreshes are now serialized with a lock on the config (~/.e2b/config.json.lock, managed by proper-lockfile: kept fresh while held, released on process exit, taken over once stale after 30 s), through a generic withFileLock(path, fn, { staleMs, timeoutMs }) in src/utils/lock.ts that feat(cli): pick projects from workspace-api, route to each project's plane #2104 also uses for project changes. The config is re-read under the lock, so a process that waited reuses the tokens the other one just wrote. A lock lost mid-refresh (the holder stalled past the stale window) prints a warning instead of crashing the command.
  • Credentials are resolved per request, not per command. sandbox logs --follow, sandbox metrics --follow and the attached sandbox create keepalive (now the static Sandbox.setTimeout, not the instance method that kept the original token) keep working past an hour.
  • e2b auth configure re-reads the config before saving the selected project. Before, it wrote back the tokens it had read before its prompt, which undid any refresh another process made while the prompt was open.
  • Config writes are atomic (temp file + rename), since refreshes now write in the background of any command.

3. The stored API key plays no role.

  • Login and configure don't write projectApiKey, and nothing reads it. The schema and the v1 → v2 migration don't know the field: a key an earlier version stored is carried through writes untouched, like any field this version doesn't know about, but the CLI makes no promise about it.
  • The project for each request comes from --project, then E2B_PROJECT_ID / E2B_TEAM_ID, then the configured project. So template list --project now selects the project for logged-in users too.
$ e2b auth login                                  # opens https://e2b.dev/auth/cli
$ e2b sandbox list                                # Bearer <access token> + X-Team-ID: <selected project>
$ E2B_PROJECT_ID=<other> e2b template list        # one-off project override
$ E2B_API_KEY=e2b_... e2b template create my-tpl  # CI: API key, no login needed

Notes for review

  • Downgrades. Released CLIs require projectApiKey in a v2 config. After a fresh login with this version there is none, so an older CLI reports the deprecated-config message and asks for e2b auth login. The changeset calls this out.
  • SDK-400 isn't fixed by this alone. Login and configure still call GET /teams here, which mints a new API key for every project server-side; the CLI just stops storing it. feat(cli): pick projects from workspace-api, route to each project's plane #2104, next in the stack, replaces that call with workspace-api /v1/me (BKND-155's CLI side; with access tokens its planned POST /api-keys step is not needed).
  • Known limit. Template.build is a single SDK call that polls build status with the credentials it started with. A build running over an hour would need a fresh token mid-call. Handling that needs a header provider in the SDK; I left it out here.

Testing

  • New tests/auth.test.ts (10 tests):
    • E2B_API_KEY precedence;
    • bearer headers and project selection (--project > E2B_PROJECT_ID > config);
    • the signed-out exit;
    • refresh storing the rotated tokens and keeping fields it doesn't know;
    • 5 concurrent callers → exactly one refresh;
    • invalid_grant → signed out;
    • stale-lock recovery;
    • tests/utils/lock.test.ts: one holder at a time, release when the holder throws, stale-lock takeover, timeout while a live holder keeps the lock;
    • the /auth/cli URL with and without E2B_DOMAIN.
      With the lock stubbed out, the concurrency and stale-lock tests fail.
  • user_config_permissions: unknown fields survive the write, and no temp file is left behind. Migration tests are updated. The command tests mock ensureAuth.
  • pnpm run test in packages/cli: 186 passed, 11 skipped (the template create backend suite needs E2B_API_KEY).
  • Against prod with my login and no E2B_API_KEY: auth info, sandbox list, template list, then sandbox create --detach → info → exec → logs → kill. All pass.
  • Real rotation: I planted an expired access token in my config (keeping the real refresh token) and ran three commands in parallel. All three succeeded, the refresh happened once, the session stayed valid, and the lock file was removed.
  • Repo-wide lint and typecheck pass.

Part of PRD-119. Related: SDK-400, BKND-155.

🤖 Generated with Claude Code

@mishushakov
mishushakov added this pull request to stack #2092 November 6, 2026 17:03
@cla-bot cla-bot Bot added the cla-signed label Nov 6, 2026
@changeset-bot

changeset-bot Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: da82a36

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

@cursor

cursor Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

PR Summary

High Risk
Replaces CLI-wide API authentication with OAuth bearer tokens, cross-process refresh locking, and config schema changes; regressions would sign users out or break CI and long-running commands.

Overview
The CLI now resolves credentials via ensureAuth and apiClient, using E2B_API_KEY when set otherwise a Bearer access token with X-Team-ID, refreshed under a proper-lockfile lock with atomic config writes. Login targets CLI_AUTH_URL (/auth/cli); projectApiKey is no longer stored or required in user config. Sandbox, template, and auth commands call ensureAuth per request so follow mode and attached create sessions survive token expiry.

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

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from e8c2f78. Download artifacts from this workflow run.

JS SDK (e2b@2.63.1-mishushakov-cli-access-token-auth.0, with @e2b/dockerfile-utils@0.1.2-mishushakov-cli-access-token-auth.0):

npm install ./e2b-dockerfile-utils-0.1.2-mishushakov-cli-access-token-auth.0.tgz ./e2b-2.63.1-mishushakov-cli-access-token-auth.0.tgz

CLI (@e2b/cli@2.23.1-mishushakov-cli-access-token-auth.0):

npm install ./e2b-cli-2.23.1-mishushakov-cli-access-token-auth.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.9.2-mishushakov-cli-access-token-auth.0):

npm install ./e2b-code-interpreter-2.9.2-mishushakov-cli-access-token-auth.0.tgz

Desktop JS SDK (@e2b/desktop@2.5.1-mishushakov-cli-access-token-auth.0):

npm install ./e2b-desktop-2.5.1-mishushakov-cli-access-token-auth.0.tgz

Python SDK (e2b==2.63.0+mishushakov.cli.access.token.auth, with e2b-dockerfile-utils==0.1.1+mishushakov.cli.access.token.auth):

pip install ./e2b_dockerfile_utils-0.1.1+mishushakov.cli.access.token.auth-py3-none-any.whl ./e2b-2.63.0+mishushakov.cli.access.token.auth-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.11.9+mishushakov.cli.access.token.auth):

pip install ./e2b_code_interpreter-2.11.9+mishushakov.cli.access.token.auth-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.7.2+mishushakov.cli.access.token.auth):

pip install ./e2b_desktop-2.7.2+mishushakov.cli.access.token.auth-py3-none-any.whl

devin-ai-integration[bot]

This comment was marked as resolved.

@linear-code

linear-code Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

PRD-119

SDK-437

SDK-400

cursor[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

@mishushakov
mishushakov removed this pull request from stack #2092 November 6, 2026 18:40
@mishushakov
mishushakov added this pull request to stack #2108 November 6, 2026 18:40
@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-auth branch from ada8b52 to 3fc9ac5 Compare November 9, 2026 17:48
cursor[bot]

This comment was marked as resolved.

@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-auth branch from 3fc9ac5 to 273309b Compare November 10, 2026 13:52
@cursor

cursor Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@mishushakov
mishushakov removed this pull request from stack #2108 November 10, 2026 14:00
@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-auth branch from 273309b to e138743 Compare November 10, 2026 14:00
@cursor

cursor Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@mishushakov
mishushakov changed the base branch from mishushakov/sdk-authorization-header to mishushakov/cli-zod-validation November 10, 2026 14:00
@mishushakov
mishushakov added this pull request to stack #2115 November 10, 2026 14:00
@mishushakov mishushakov reopened this Nov 10, 2026
@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-auth branch from e138743 to 6bf920c Compare November 10, 2026 14:32
@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-auth branch 2 times, most recently from 1af5a6d to 486080f Compare November 11, 2026 14:23
@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-auth branch from 486080f to f6ef88d Compare November 11, 2026 14:29
cursor[bot]

This comment was marked as resolved.

Base automatically changed from mishushakov/cli-zod-validation to main November 11, 2026 14:41
mishushakov and others added 2 commits November 11, 2026 15:41
… key

- Login opens https://e2b.dev/auth/cli (https://$E2B_DOMAIN/auth/cli);
  DOCS_BASE and the E2B_DOCS_BASE override are removed.
- ensureAuth() replaces ensureAPIKey(): E2B_API_KEY (CI) wins, otherwise
  `Authorization: Bearer <access token>` + `X-Team-ID: <project>`, with
  the project from --project, E2B_PROJECT_ID or the config.
- Access tokens are refreshed before they expire. Refresh tokens rotate,
  so refreshes are serialized across processes with a lock file and the
  config is re-read under the lock; previously two commands refreshing
  at once could get invalid_grant and delete the config.
- Credentials are resolved per request, so `logs --follow`, `metrics
  --follow` and the attached `sandbox create` keepalive keep working past
  the one-hour token lifetime.
- `auth configure` re-reads the config before writing, so it no longer
  overwrites tokens another process refreshed while its prompt was open.
- projectApiKey is no longer stored; an old one is dropped on the next
  write. Config writes are atomic (temp file + rename).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…epalive

- Auth is derived from e2b.ConnectionOpts so a renamed SDK option fails
  to compile instead of being dropped by the spreads.
- isRunning lets credential errors propagate; logs --follow no longer
  reports a running sandbox as closed when the token refresh fails.
- The attached-create keepalive logs a failed setTimeout instead of
  leaving an unhandled rejection that terminates the session.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mishushakov
mishushakov marked this pull request as ready for review November 11, 2026 14:41
@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-auth branch from f6ef88d to 53bea5c Compare November 11, 2026 14:41
chatgpt-codex-connector[bot]

This comment was marked as resolved.

@chatgpt-codex-connector

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T14:45:39.694491Z 53bea5c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

cursor[bot]

This comment was marked as resolved.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread packages/cli/src/user.ts Resolved
Comment thread packages/cli/src/user.ts Resolved
The cross-process lock that serializes token refreshes now lives in
src/utils/lock.ts as withFileLock(path, fn, { staleMs, timeoutMs }): it
takes an O_EXCL lock file, runs fn, and always releases it, taking over
locks older than staleMs left by crashed processes. Both timings are
required, since the stale time must outlast whatever runs under the lock.

Token refresh uses it with ~/.e2b/config.json.lock. The invalid_grant
sign-out still happens under the lock.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

`withFileLock` now locks the config file through proper-lockfile instead
of a hand-rolled O_EXCL file: the lock is kept fresh while held so a live
holder is never taken over, it is released when the process exits, and
waiting uses bounded retries. Callers pass the config path; the lock is
`config.json.lock` as before, now a directory.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

…n a lost lock

The project API key is no longer used for requests, but a key saved by an
earlier CLI version is kept on disk (and a v1 `teamApiKey` is carried over
as `projectApiKey`) so those versions stay signed in next to this one.

`withFileLock` replaces proper-lockfile's default `onCompromised`, which
throws from a timer and would take the command down, with a warning.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread packages/cli/src/utils/lock.ts Resolved
Comment thread packages/cli/src/user.ts Resolved
This version neither reads, migrates nor strips `projectApiKey`: it is
one more field earlier versions wrote, carried through writes like any
field this version doesn't know about. The changeset says what that
means for a downgrade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@e2b-harness e2b-harness Bot added the review-requested Posted in #code-review-requests on Slack label Nov 12, 2026
@mishushakov
mishushakov merged commit c0211e9 into main Nov 12, 2026
40 checks passed
@mishushakov
mishushakov deleted the mishushakov/cli-access-token-auth branch November 12, 2026 12:31
mishushakov added a commit that referenced this pull request Nov 12, 2026
…plane (#2104)

## Summary

`e2b auth login` and `e2b auth configure` stop calling the API's `GET
/teams` and read the user's workspaces and projects from workspace-api
`GET /v1/me` instead. Requests then go to the **selected project's own
control plane**, with `Authorization: Bearer <access token>` and
`X-Team-ID: <project>`. Stacked on #2103 (stack #2115); docs in
e2b-dev/docs#637.

This is the CLI half of BKND-155, minus its API-key step: since #2103
the CLI uses the access token directly, so no key is minted at all. That
also ends SDK-400's key sprawl from the CLI side, because `GET /teams`
minted a key in every project on each call.

## Login / configure

1. `GET https://workspace-api.e2b.dev/v1/me` with the access token.
Every user is assumed to be set up there: a 401 is reported as a
rejected session (login: "Your login was not accepted by workspace-api,
please contact support"; other commands: "Your session was not accepted.
Run `e2b auth login` again."). There is no `POST /v1/users/bootstrap`
call.
2. Login asks for the workspace and then for one of its active projects,
both steps shown even with a single choice (`pickProject`, also used by
`auth configure` and `project use` in #2105). The active project in the
workspace with `kind: personal` is preselected, the rule workspace-api
uses for `is_default`; SSO-only users have none, so nothing is
preselected. Without a terminal, that default is taken as is, else the
first active project. Blocked projects print a warning (`Project X is
blocked (credit_exhausted), …`).
3. The config stores `projectId` (UUID), `projectName` and the new
`projectDomain` (the project's `control_plane_domain`). `auth configure`
is the same picker with the current project preselected. It writes
through `updateUserConfig()`, which holds the token-refresh lock.

**workspace-api URL:** it only exists at `workspace-api.e2b.dev`, while
the CLI's API default is `e2b.app`. `E2B_DOMAIN` values
`e2b.app`/`e2b.dev` (or unset) map to `https://workspace-api.e2b.dev`,
any other domain to `https://workspace-api.<domain>`, and
`E2B_WORKSPACE_API_URL` overrides it. Logging in needs workspace-api, so
a custom `E2B_DOMAIN` is supported with `E2B_API_KEY` only; there is no
fallback to `GET /teams` or to the stored project.
`workspace-api.e2b.app` returns 400. The `/v1/me` response is validated
with zod (only the fields the CLI uses; `status`/`kind` kept as plain
strings so a new server-side value doesn't break the CLI).

## Why per-project domains

Your projects live on five planes. Measured on prod with `GET
/sandboxes`, Bearer + `X-Team-ID`:

| Project's control plane | via `api.e2b.app` | via
`api.<control_plane_domain>` |
| --- | --- | --- |
| e2b-foxtrot.dev | 200 | 200 |
| e2b-juliett.dev | 401 | 200 |
| e2b-tango.dev | 401 | 200 |
| foxtrot-2.e2b.dev | 401 | 200 |
| foxtrot-3.e2b.dev | 401 | 200 |

So `ensureAuth()` now returns the project's domain along with the
headers, and the SDK sends both the API calls and the sandbox (envd)
traffic there. `template create` no longer pins the default domain.
Configs written by earlier versions have no `projectDomain`; it's looked
up in `/v1/me` once and saved.

`E2B_PROJECT_ID` now resolves through `/v1/me` (fetched once per
process) by UUID, `prj_` public ID, slug or unambiguous name, so it also
routes to the right plane. A slug or name that matches projects in
several workspaces is rejected with the candidates listed. `E2B_API_KEY`
is unchanged: the key decides the project and the default domain
applies.

`template list` no longer sends the project as the `teamID` query param:
that param accepts IDs only, while `X-Team-ID` (or the API key) already
selects the project, so a slug or name in `--project`/`E2B_PROJECT_ID`
works there too. `resolveProjectId()` had no other caller and is gone.

`updateUserConfig()` also takes a function of the config as it is under
the lock. The one-time `projectDomain` lookup uses that to patch only
the project still selected, so an `e2b auth configure` that switches
projects in the meantime can't end up with the other project's domain.

## Testing

- `tests/workspaces.test.ts` (27):
  - URL mapping and override;
- personal-project rule, SSO-only users and non-active projects skipped;
- `findProject` by every form, ambiguous names, a slug shared across
workspaces, slugs winning over names, unknown projects;
- `pickProject` with no active project and without a terminal; with a
terminal: workspace then project, single-choice steps still asked, a
given workspace only;
  - `findProject` scoped to a workspace;
  - `fetchMe` 401 → undefined, bearer header, schema rejection;
  - the blocked warning.
- `tests/auth.test.ts` (+3): the stored project needs no lookup; the
one-time domain lookup is saved; `E2B_PROJECT_ID` and `--project`
resolve to UUID + domain with one `/v1/me` call; unknown project exits
with a hint.
- `pnpm run test` in `packages/cli`: 216 passed, 11 skipped (the
`template create` backend suite needs `E2B_API_KEY`).
- **On prod with my login:**
- `sandbox list` on a config from #2103 looked up and stored
`e2b-foxtrot.dev`;
  - `E2B_PROJECT_ID=<juliett project>` listed fine;
- on a tango project: `sandbox create` → `exec` → `info` → `kill` all
worked;
  - an unknown `E2B_PROJECT_ID` exits with the not-found message.
- Tested before the two-step picker and the `template list` change;
those are covered by the unit tests.
- Login itself wasn't re-run end to end (that signs out). The
personal-project and picker paths are covered by the unit tests above.
- Repo-wide lint and typecheck pass.

Part of PRD-119. Implements BKND-155 (CLI side); related: SDK-400.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mishushakov added a commit that referenced this pull request Nov 13, 2026
## Summary

`E2B_ACCESS_TOKEN` authenticates the CLI with an OAuth access token, for
CI and other places where nobody can run `e2b auth login`. Based on
`main`, which has #2103–#2105 merged (#2124 was closed, so this no
longer stacks on it). Docs: to be added to e2b-dev/docs#636 before
release.

```console
$ E2B_ACCESS_TOKEN=<token> E2B_PROJECT_ID=prj_… e2b sandbox list    # the project for every request
$ E2B_ACCESS_TOKEN=<token> E2B_PROJECT_ID=web-app e2b template list  # by name or slug works too
$ E2B_ACCESS_TOKEN=<token> e2b project list                        # listing needs no project: find the one to set
$ E2B_ACCESS_TOKEN=<token> e2b sandbox list
E2B_ACCESS_TOKEN needs E2B_PROJECT_ID: set it to the project requests go to (its ID, prj_ ID, slug or name). Run e2b project list to see your projects.
$ E2B_ACCESS_TOKEN=<legacy dashboard token> E2B_API_KEY=e2b_… e2b auth info
E2B_ACCESS_TOKEN is not an OAuth access token and is ignored: access tokens from the dashboard are no longer supported. Unset it to remove this warning.
Authenticating with E2B_API_KEY.
```

## How

- **Precedence:** `E2B_API_KEY` or `E2B_ACCESS_TOKEN`, then the stored
login. Setting both is refused before any request (`Both E2B_API_KEY and
E2B_ACCESS_TOKEN are set. Unset one of them.`), since the key selects
its own project and the token its user's, so there is no sensible
precedence. While the token is set the stored login is ignored entirely,
including its selected project: the token may belong to another user.
- **Project:** `E2B_PROJECT_ID` is required for every request and is the
project for it, like `E2B_API_KEY` decides the project on its own. It is
resolved through `/v1/me` (ID, `prj_` ID, slug or name) for the UUID
`X-Team-ID` carries and the project's control plane. `--project` plays
no part, as with the key. Without it a command that sends requests exits
before any of them: `E2B_ACCESS_TOKEN needs E2B_PROJECT_ID: set it to
the project requests go to (its ID, prj_ ID, slug or name). Run e2b
project list to see your projects.` There is no fallback project:
`/v1/me` has no default, and a guessed one would send CI writes to the
wrong project silently.
- **No refresh:** there is no refresh token, so the token is used as is.
JWT claims are read with [jose](https://github.com/panva/jose)
(`decodeJwt`, no signature check: the API verifies the token; this
replaces the hand-rolled base64url decoding the refresh logic used too).
A JWT whose `exp` has passed is rejected before any request with
`E2B_ACCESS_TOKEN has expired. Set a current one, or unset it and run
e2b auth login.` A value that isn't a JWT at all is a legacy dashboard
token (the variable name used to carry those, and some shells and CI
jobs still export them): it is ignored as if unset, so the stored login
or `E2B_API_KEY` is used (and it doesn't count as setting both), with a
warning on stderr: `E2B_ACCESS_TOKEN is not an OAuth access token and is
ignored: access tokens from the dashboard are no longer supported. Unset
it to remove this warning.` A token workspace-api rejects fails with
`E2B_ACCESS_TOKEN was rejected`.
- **Commands:** `requireMe()` accepts the token, so `workspace list`,
`project list` and `project info` work without a login, with
`E2B_PROJECT_ID`'s project as the current one. `workspace list` and
`project list` don't need `E2B_PROJECT_ID` (no project is marked
current), so they can be used to find it; `project info` without it
shows the missing-`E2B_PROJECT_ID` error. `e2b project use` and `e2b
auth configure` refuse with the token set, since the stored selection
wouldn't be used and the picker would show the token user's projects,
and point at `E2B_PROJECT_ID`. `e2b auth info` reports the credential in
use and that a stored login is not used, warns (without failing) when
the token has no `E2B_PROJECT_ID`, and exits with the same error when
both variables are set. The signed-out box mentions the variable next to
`E2B_API_KEY`.

## Testing

- `tests/auth.test.ts` (+10): `auth info` with the token and no
`E2B_PROJECT_ID` reporting the token and warning, token +
`E2B_PROJECT_ID` with the stored login and `--project` ignored (and
`/v1/me` asked with the env token), the missing-`E2B_PROJECT_ID` error
from `ensureAuth` pointing at `e2b project list`, `requireMe` listing
projects without `E2B_PROJECT_ID`, both variables together refused, an
expired token rejected before any request, a non-JWT legacy value
ignored with a warning (the stored login is used) and not refused next
to `E2B_API_KEY` (the key is used), `requireMe` without a login, a
rejected token named as such.
- `tests/commands/project.test.ts` (+5): `project list` marks
`E2B_PROJECT_ID`'s project current with the token and marks none without
it, `project info` without it asks for it, `project use` and `auth
configure` refuse.
- `pnpm run test` in `packages/cli`: 253 passed, 11 skipped;
`tests/commands/template/create.test.ts` fails because its backend suite
needs `E2B_API_KEY`. Lint and typecheck pass.
- Checked with the built CLI: the missing-`E2B_PROJECT_ID` error, and
the legacy-token warning next to `E2B_API_KEY` (output above).
- Not run against prod: I don't have an access token outside my login's
config to test with.

Part of PRD-119.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed review-requested Posted in #code-review-requests on Slack

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants