Skip to content

refactor(sdk): remove dead code and de-duplicate SDK/CLI helpers - #1766

Draft
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787555919-sdk-dead-code-cleanup
Draft

refactor(sdk): remove dead code and de-duplicate SDK/CLI helpers#1766
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787555919-sdk-dead-code-cleanup

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Dead-code / duplication audit of the hand-written SDK sources (packages/js-sdk/src, packages/python-sdk/e2b, packages/cli/src; generated clients, fixtures and build output excluded). Behavior-preserving: every candidate was grepped across the whole monorepo (source, tests, examples, docs) before touching it, and nothing exported from a package entry point was removed.

Removed (verified unreferenced monorepo-wide)

  • asBuildLogs() in packages/cli/src/utils/format.ts — zero references anywhere.
  • VolumeApiPaths type alias in packages/js-sdk/src/volume/client.ts — zero references; it is not re-exported from volume/index.ts or the package entry point, so it is not part of the public surface.
  • Local REQUEST_TIMEOUT_MS = 60_000 duplicate in volume/client.ts — now imports the same constant from connectionConfig (identical value).

De-duplicated

  • Sandbox.uploadUrl() / Sandbox.downloadUrl() (JS) were byte-for-byte identical except the 'write'/'read' operation and upload's path ?? '' default. Both now delegate to one private fileOperationUrl(path, operation, opts):

    async uploadUrl(path?: string, opts?: SandboxUrlOpts) {
      return await this.fileOperationUrl(path ?? '', 'write', opts)
    }
    async downloadUrl(path: string, opts?: SandboxUrlOpts) {
      return await this.fileOperationUrl(path, 'read', opts)
    }
  • Same change applied to the Python SDK in e2b/sandbox/main.py via SandboxBase._file_operation_url(path, operation, ...). It lives on the shared base, so sync and async both pick it up.

  • packages/js-sdk/src/volume/index.ts: the repeated 404 -> VolumePathNotFoundError + handleApiError + !res.data + convertVolumeEntryStat blocks collapse into throwOnVolumePathError(res, path) and volumeEntryStatFromResponse(res, path), used by list, makeDir, getInfo, updateMetadata, writeFile, readFile and remove. The streamed readFile path keeps its own inline handling because it must cancel the unconsumed body and run cleanup() before throwing.

  • packages/js-sdk/src/secret.ts: create, update and getInfo shared an identical error/empty-body/convert tail, now secretInfoFromResponse(res). The per-method 404 handling (SecretNotFoundError vs. exists() returning false) stays at the call sites since it differs.

  • CLI: init and migrate each hard-coded the same three-entry language select list; both now use languageChoices from template/generators/types.ts (prompt messages stay per-command).

Public-API candidates deliberately left in place

  • validateApiKey, apiErrorFromCode, handleApiError, components, paths (js-sdk/src/api) — exported from the entry point; removal would be breaking even where internal usage is thin.
  • Deprecated volume type aliases (VolumeMetadataOptions, VolumeWriteOptions) — explicitly kept for backwards compatibility.
  • Column<T> in packages/cli/src/utils/table.ts — only used in renderTable's own signature, but it is the documented shape callers construct.
  • Python sync/async sandbox, template, volume and secret methods that static analysis (vulture) flags as unused — they are the user-facing API; static confidence is insufficient and removal would be breaking.
  • ConnectionConfig.set_integration() (Python) and SupportsApiErrorResponse — flagged by static analysis but genuinely used (CLI/tests, and api_exception_from_response respectively).

No changeset: no public surface changed (the two removals are not entry-point exports and all other changes are internal refactors).

Verification

pnpm run format, pnpm run lint and pnpm run typecheck clean across all three packages. CLI suite: 116 passed. JS SDK offline suites covering the touched code (sandbox/urls, sandbox/files/signing, sandbox/secure, utils, mocked volume/file, secret): passed. Python tests/test_sandbox_urls.py: passed. Added a regression test asserting uploadUrl() with no path still omits the path query param and signs the empty path, since that default now flows through the shared helper.

Link to Devin session: https://app.devin.ai/sessions/c4b115a6dec24b8aad92fbd5d24c7610

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@cla-bot cla-bot Bot added the cla-signed label Aug 24, 2026
@changeset-bot

changeset-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 501ea4a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from 5c4c4b7. Download artifacts from this workflow run.

JS SDK (e2b@2.45.1-devin-1787555919-sdk-dead-code-cleanup.0):

npm install ./e2b-2.45.1-devin-1787555919-sdk-dead-code-cleanup.0.tgz

CLI (@e2b/cli@2.17.2-devin-1787555919-sdk-dead-code-cleanup.0):

npm install ./e2b-cli-2.17.2-devin-1787555919-sdk-dead-code-cleanup.0.tgz

Python SDK (e2b==2.45.1+devin.1787555919.sdk.dead.code.cleanup):

pip install ./e2b-2.45.1+devin.1787555919.sdk.dead.code.cleanup-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.

Reviewed against TASTE.md from e2b-dev/sdk-harness, focused only on lines this PR touches. Checked: API shape (T-3, T-11, T-15), cross-language parity (T-1, T-2), errors (T-57, T-58, T-62), timeout constants (T-47), enums/named types (T-15), docstrings (T-69/T-71).

4 violations found, all in the newly extracted helpers (the de-duplication itself is a good change — REQUEST_TIMEOUT_MS moving into connectionConfig is exactly T-47, and _file_operation_url / fileOperationUrl keep JS and Python mirrors in step per T-1):

  • 2× bare Error in the new response helpers instead of the domain error class (T-57/T-58, message also fails T-62). The lines are carried over from the call sites being collapsed, but centralizing them is the cheap moment to fix them once instead of five times.
  • Python helper re-inlines Literal["read", "write"] instead of the existing Operation alias (T-11).
  • Chained optional positionals at the new helper call sites (T-3).

Nothing line-specific for the CLI changes: languageChoices is CLI surface, outside the SDK design rules, and the extraction preserves behavior. FetchResponse<any, any, any> in the new helpers matches the existing handleApiError signature, so it is consistent, not a new violation.

}

if (!res.data) {
throw new Error('Response data is missing')

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-57/T-58 — one base class per domain, and prefer the specific error over a generic one: secret failures must be SecretError (already imported here), never a bare Error, or a caller's catch (e) { if (e instanceof SecretError) } misses this path entirely. T-62 also wants the message to say what to do, not just what failed.

Suggested change
throw new Error('Response data is missing')
throw new SecretError(
'The API returned a success status with an empty body. Retry the request, and contact support@e2b.dev if it keeps happening.'
)

throwOnVolumePathError(res, path)

if (!res.data) {
throw new Error('Response data is missing')

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-57/T-58 — same as in secret.ts: volume errors extend VolumeError, so the empty-body case should throw VolumeError rather than a bare Error that escapes every domain-scoped catch. T-62: name the recovery in the message.

Suggested change
throw new Error('Response data is missing')
throw new VolumeError(
'The API returned a success status with an empty body. Retry the request, and contact support@e2b.dev if it keeps happening.'
)

def _file_operation_url(
self,
path: str,
operation: Literal["read", "write"],

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-11 — when a concept already has a named type, use it instead of the primitive/literal it aliases. Operation = Literal["read", "write"] already exists in e2b/sandbox/signature.py and is what get_signature (called a few lines below) takes; re-inlining the literal here creates a second place the set is written down, which is exactly what the alias exists to prevent.

Add Operation to the existing from .signature import get_signature import; Literal then drops out of the typing import added at the top of the file.

Suggested change
operation: Literal["read", "write"],
operation: Operation,


:return: URL for downloading file
"""
return self._file_operation_url(path, "read", user, use_signature_expiration)

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-3 — the golden rule applies from the very first optional parameter: path and operation are required and positional, but user / use_signature_expiration are optional and belong in kwargs (the Python spelling of the trailing options object). As written the call site is a chain of optional positionals whose meaning is unreadable without jumping to the definition, and inserting a parameter later silently reorders callers. Same on the upload_url call below.

Suggested change
return self._file_operation_url(path, "read", user, use_signature_expiration)
return self._file_operation_url(
path,
"read",
user=user,
use_signature_expiration=use_signature_expiration,
)

*/
private async fileOperationUrl(
path: string,
operation: 'read' | 'write',

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-1/T-11 — the Python mirror of this helper takes the named Operation alias from signature.py, so the JS side should have the same named type rather than a third inline copy of 'read' | 'write' (the other copy is in signature.ts). Export export type Operation = 'read' | 'write' from src/sandbox/signature.ts, use it in getSignature, and reference it here so both SDKs write the vocabulary down once.

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

Looks good, straightforward de-duplication refactor. I compared each extracted helper (JS fileOperationUrl, Python _file_operation_url, secretInfoFromResponse, throwOnVolumePathError/volumeEntryStatFromResponse, CLI languageChoices) line-by-line against the original inline code they replace, and the logic is preserved exactly (including the path ?? '' default only applying to upload, and optional-chaining on opts being equivalent to the prior opts = opts ?? {}). Confirmed REQUEST_TIMEOUT_MS moved to connectionConfig has the identical value (60_000) and that asBuildLogs/VolumeApiPaths have zero remaining references anywhere in the repo. Python's SandboxBase is the single shared sync/async base, so no separate async change was needed.

Extended reasoning...

Overview

This PR is a pure refactor/dead-code-removal pass across the JS SDK, Python SDK, and CLI: it extracts previously duplicated logic into shared helpers (Sandbox.fileOperationUrl / Python SandboxBase._file_operation_url, secretInfoFromResponse, throwOnVolumePathError/volumeEntryStatFromResponse, CLI languageChoices), removes an unused CLI helper (asBuildLogs) and an unused type alias (VolumeApiPaths), and de-duplicates a REQUEST_TIMEOUT_MS constant by importing it from connectionConfig instead of redefining it locally.

Security risks

None identified. No new external input handling, no auth/crypto logic changes — the signing (getSignature) and error-handling code paths are moved verbatim into shared helpers, not altered in behavior.

Level of scrutiny

I read every changed file's diff in full and compared old vs. new logic side-by-side for each extraction (sandbox URL signing, secret response handling, volume error/stat handling, CLI language choices, and the REQUEST_TIMEOUT_MS/VolumeApiPaths cleanups). I also grepped the repo to confirm the removed asBuildLogs and VolumeApiPaths symbols have zero remaining references, and confirmed the REQUEST_TIMEOUT_MS constant value is identical in both locations. This is a mechanical, low-risk refactor with no behavior change, matching the PR's own description, so a lighter level of scrutiny than a feature PR is appropriate — but I did verify each extraction line-by-line rather than trusting the description.

Other factors

A new regression test (urls.test.ts) covers the one behavior detail worth double-checking (upload URL with no path, for both unsecured and signed sandboxes), and it passes trivially given the code inspected. The Python side reuses the single shared SandboxBase for both sync and async, so CLAUDE.md's "apply equivalent changes to JS and both sync/async Python" requirement is satisfied without needing a second file. No changeset was added, which is consistent with CLAUDE.md's requirement only applying when public surface changes — here the removed/renamed items are private/unexported and behavior is preserved, so this looks correctly scoped.
approve

@mishushakov

Copy link
Copy Markdown
Member

putting this in drafts right now - will be revisiting it later.

@mishushakov
mishushakov marked this pull request as draft August 25, 2026 13:04
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