Skip to content

.copy() Method Failure with Tarball Files #1028

@khalid-halo

Description

@khalid-halo

Problem Description

The E2B Template SDK's .copy() method consistently fails with "exit status 2" when attempting to copy .tgz (tarball) files to the template during build. This prevents installation of npm packages from local tarballs, forcing reliance on external registries.

Error Message

Build failed: error building step 12: failed to extract files: exit status 2

Expected Behavior

The .copy() method should successfully copy .tgz files to the template filesystem, allowing subsequent installation via npm install -g /path/to/package.tgz.

Actual Behavior

  • .copy() fails immediately when source file is a .tgz archive
  • Error occurs during template build phase
  • Same .tgz files work correctly when manually transferred or base64-encoded

Reproduction Steps

Step 1: Create Valid npm Package Tarballs

# Create test packages
mkdir test-packages && cd test-packages

# Create package 1
mkdir test-pkg-1
cat > test-pkg-1/package.json << 'EOF'
{
  "name": "@test/pkg-1",
  "version": "1.0.0",
  "description": "Test package 1"
}
EOF

# Create package 2
mkdir test-pkg-2
cat > test-pkg-2/package.json << 'EOF'
{
  "name": "@test/pkg-2",
  "version": "1.0.0",
  "description": "Test package 2"
}
EOF

# Build tarballs
cd test-pkg-1 && npm pack && cd ..
cd test-pkg-2 && npm pack && cd ..

# Move tarballs to packages directory
mkdir -p packages
mv test-pkg-1/test-pkg-1-1.0.0.tgz packages/
mv test-pkg-2/test-pkg-2-1.0.0.tgz packages/

# Verify tarballs are valid
tar -tzf packages/test-pkg-1-1.0.0.tgz | head -5
tar -tzf packages/test-pkg-2-1.0.0.tgz | head -5

Result: Valid tarball files created (verified with tar -tzf)

Step 2: Create E2B Template with .copy()

// template.ts
import { Template } from 'e2b'

export const template = Template()
  .fromNodeImage('lts')
  .setUser('user')

  // Attempt 1: Copy to /tmp as root
  .setUser('root')
  .copy('./packages/test-pkg-1-1.0.0.tgz', '/tmp/test-pkg-1-1.0.0.tgz')
  .copy('./packages/test-pkg-2-1.0.0.tgz', '/tmp/test-pkg-2-1.0.0.tgz')
  .setUser('user')

  // Try to install from copied tarballs
  .runCmd('npm install -g /tmp/test-pkg-1-1.0.0.tgz /tmp/test-pkg-2-1.0.0.tgz')
  .setUser('user')
// build.ts
import 'dotenv/config'
import { Template } from 'e2b'
import { template } from './template'

async function build() {
  console.log('Building template...')

  const result = await Template.build(template, {
    alias: 'test-copy-issue',
    cpuCount: 1,
    memoryMB: 1024
  })

  console.log('Template ID:', result.templateId)
}

build().catch(console.error)
// package.json
{
  "name": "e2b-copy-issue-test",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsx build.ts"
  },
  "dependencies": {
    "e2b": "^2.3.0",
    "dotenv": "^16.0.0"
  },
  "devDependencies": {
    "tsx": "^4.0.0",
    "typescript": "^5.0.0"
  }
}

Step 3: Run Build

npm install
npm run build

Step 4: Observe Failure

Build Output:

Building template...
  Build started
  Requesting build for template: test-copy-issue
  Template created with ID: xxxxxxxxxx, Build ID: xxxxxxxxxx
  Uploaded './packages/test-pkg-1-1.0.0.tgz'
  Uploaded './packages/test-pkg-2-1.0.0.tgz'
  All file uploads completed
  Starting building...
  Waiting for logs...
  Building template xxxxxxxxxx/xxxxxxxxxx
  [base] FROM node:lts [...]
  ...
  [builder 12/13] COPY ./packages/test-pkg-1-1.0.0.tgz /tmp/test-pkg-1-1.0.0.tgz
  Build failed: error building step 12: failed to extract files: exit status 2
  Build finished

❌ Build failed: failed to extract files: exit status 2

Variations Tested

Variation 1: Copy to Different Paths

Tested paths:

  • /tmp/package.tgz ❌ Failed
  • /home/user/package.tgz ❌ Failed
  • /var/tmp/package.tgz ❌ Failed
  • /opt/package.tgz ❌ Failed

Result: All failed with same error

Variation 2: Different User Contexts

// As root
.setUser('root')
.copy('./packages/test-pkg-1-1.0.0.tgz', '/tmp/test-pkg-1-1.0.0.tgz')
// ❌ Failed

// As user
.setUser('user')
.copy('./packages/test-pkg-1-1.0.0.tgz', '/home/user/test-pkg-1-1.0.0.tgz')
// ❌ Failed

// With explicit permissions (if supported)
.copy('./packages/test-pkg-1-1.0.0.tgz', '/tmp/test-pkg-1-1.0.0.tgz', {
  user: 'root',
  mode: 0o644
})
// ❌ Failed (TypeScript error - parameters not documented)

Result: All failed regardless of user context

Variation 3: Different File Types

// JSON file
.copy('./config/test.json', '/tmp/test.json')
// ✅ SUCCESS

// Text file
.copy('./config/test.txt', '/tmp/test.txt')
// ✅ SUCCESS

// .tgz file
.copy('./packages/test-pkg-1-1.0.0.tgz', '/tmp/test.tgz')
// ❌ FAILED

// .tar.gz file (renamed from .tgz)
.copy('./packages/test-pkg-1-1.0.0.tar.gz', '/tmp/test.tar.gz')
// ❌ FAILED

// .zip file
.copy('./packages/test-pkg-1-1.0.0.zip', '/tmp/test.zip')
// ❌ FAILED (tested with zipped tarball)

Result: Only text-based files (JSON, TXT) succeed; binary archives fail

Variation 4: macOS Extended Attributes

# Check for macOS extended attributes
xattr -l packages/*.tgz

# Output:
# test-pkg-1-1.0.0.tgz: com.apple.provenance:
# test-pkg-2-1.0.0.tgz: com.apple.provenance:

# Attempt to remove attributes
xattr -c packages/*.tgz
xattr -d com.apple.provenance packages/*.tgz

# Re-run build
npm run build

Result: Still failed - macOS attributes not the cause


Workarounds Attempted

Workaround 1: Use runCmd with base64 Encoding ⚠️ PARTIAL SUCCESS

.runCmd(`
  # Decode base64 tarball
  echo "H4sIAAAAAAAAA..." | base64 -d > /tmp/package.tgz
  npm install -g /tmp/package.tgz
`)

Issues:

  • Works but requires encoding tarballs as base64
  • Very long command strings (base64 can be large)
  • Difficult to maintain
  • Not scalable for multiple packages

Workaround 2: Download from External Source ⚠️ PARTIAL SUCCESS

.runCmd(`
  curl -o /tmp/package.tgz https://example.com/package.tgz
  npm install -g /tmp/package.tgz
`)

Issues:

  • Requires external hosting
  • Defeats purpose of local package installation
  • Network dependency during build
  • Security concerns with external URLs

Workaround 3: Use Private npm Registry ✅ WORKING SOLUTION

.runCmd(`
  cat > /home/user/.npmrc << 'EOF'
@scope:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=\${GITHUB_TOKEN}
EOF
`)
.npmInstall(['@scope/[email protected]'], { g: true })

Issues:

  • Requires publishing to registry (GitHub Packages, Verdaccio, etc.)
  • Additional infrastructure/setup required
  • Not suitable for unpublished packages
  • Doesn't solve the underlying .copy() issue

Environment Details

System Information

# Operating System
uname -a
# Darwin 24.3.0 Darwin Kernel Version 24.3.0

# Node.js Version
node --version
# v20.18.1

# npm Version
npm --version
# 10.8.2

# E2B SDK Version
npm list e2b
# [email protected]

Template Context

// E2B SDK Import
import { Template } from 'e2b'

// SDK Version
import { version } from 'e2b/package.json'
// 2.3.0

Package File Details

# File sizes
ls -lh packages/*.tgz
# -rw-r--r--  32K test-pkg-1-1.0.0.tgz
# -rw-r--r--  21K test-pkg-2-1.0.0.tgz

# File types
file packages/*.tgz
# test-pkg-1-1.0.0.tgz: gzip compressed data
# test-pkg-2-1.0.0.tgz: gzip compressed data

# Validate archives
tar -tzf packages/test-pkg-1-1.0.0.tgz > /dev/null && echo "Valid"
# Valid

# Check for corruption
gzip -t packages/test-pkg-1-1.0.0.tgz && echo "OK"
# OK

Additional Observations

1. Upload Success but Copy Failure

Notable: Files are successfully uploaded to E2B build environment:

Uploaded './packages/test-pkg-1-1.0.0.tgz'
Uploaded './packages/test-pkg-2-1.0.0.tgz'

But then fail during COPY step in Dockerfile:

[builder 12/13] COPY ./packages/test-pkg-1-1.0.0.tgz /tmp/test-pkg-1-1.0.0.tgz
Build failed: error building step 12: failed to extract files: exit status 2

Question: Is E2B attempting to extract/decompress tarballs automatically during COPY?

2. Error Code Analysis

Exit Status 2 typically means:

  • File not found (ENOENT)
  • Permission denied (EACCES)
  • Invalid argument (EINVAL)

However:

  • Files are confirmed uploaded
  • Permissions should not be an issue (running as root)
  • Arguments appear valid

Hypothesis: E2B may be treating .tgz files specially (attempting extraction) and failing

3. Comparison with Other SDKs

Tested with Docker directly:

FROM node:lts
COPY ./packages/test-pkg-1-1.0.0.tgz /tmp/
RUN npm install -g /tmp/test-pkg-1-1.0.0.tgz

Result: ✅ Works perfectly with Docker

Conclusion: Issue is specific to E2B's .copy() implementation


Impact Assessment

Current Impact

Affected Use Cases:

  1. Installing npm packages from local tarballs in templates
  2. Pre-installing private npm packages without registry
  3. Offline package installation scenarios
  4. Monorepo package installations

Workaround Limitations:

  • Base64 encoding: Not scalable, difficult to maintain
  • External hosting: Security concerns, network dependency
  • Private registry: Requires additional infrastructure, not always possible

Business Impact

For our project:

  • Cannot install 3 private npm packages (@sl8/ai-base, @sl8/ai-video, @sl8/ai-image)
  • Forced to use GitHub Packages (adds complexity)
  • Alternative workarounds add 10-20 seconds to sandbox startup
  • Developer experience degraded

General impact:

  • Limits offline development scenarios
  • Forces dependency on external registries
  • Increases build complexity
  • Reduces template portability

Questions for E2B Team

  1. Is .copy() attempting to extract tarball files automatically?

    • If yes, can this behavior be disabled?
    • Is there a flag to copy files as-is without extraction?
  2. Is this a known limitation or bug?

    • Are binary files supported by .copy()?
    • Is there official documentation on file type limitations?
  3. What is the recommended approach for installing local npm packages?

    • Should we use base64 encoding?
    • Is there a better method we're missing?
  4. Could you provide more detailed error messages?

    • "exit status 2" is not very descriptive
    • What is the underlying failure reason?
    • Can build logs show more detail?
  5. Is there an alternative method to copy binary files?

    • .addFile() method?
    • Different copy syntax?
    • Direct filesystem manipulation?
  6. Are there plans to fix or improve this?

    • Is this on the roadmap?
    • Estimated timeframe for fix?
    • Any beta features we can test?

Requested Support

Immediate Needs

  1. Clarification on whether this is expected behavior or a bug
  2. Workaround that doesn't require external infrastructure
  3. Documentation on .copy() method limitations and supported file types

Long-term Needs

  1. Fix for .copy() to handle binary files (tarballs, zip, etc.)
  2. Enhanced error messages for easier debugging
  3. Documentation of best practices for package installation

Reproducible Test Case

We can provide a complete minimal reproducible example:

Repository: https://github.com/StartHalo/sl8-training
Branch: claude/e2b-copy-issue-reproduction
Path: e2b/test-copy-issue/

Contents:

  • template.ts - Minimal template demonstrating issue
  • build.ts - Build script
  • packages/ - Sample tarballs that fail
  • README.md - Step-by-step reproduction instructions

To reproduce:

git clone https://github.com/StartHalo/sl8-training.git
cd sl8-training/e2b/test-copy-issue
npm install
export E2B_API_KEY="your-key-here"
npm run build  # Will fail at COPY step

Additional Information Available

We can provide:

  • ✅ Complete build logs
  • ✅ Sample tarball files
  • ✅ Template source code
  • ✅ Screen recording of reproduction
  • ✅ Stack traces (if needed)
  • ✅ Network logs during build

Contact Information

Project: SL8 AI Training Platform
Use Case: E2B templates for AI-powered development sandboxes
Timeline: Need resolution within 2 weeks for production deployment

Primary Contact:

  • Name: [Your Name]
  • Email: [Your Email]
  • GitHub: StartHalo/sl8-training
  • Preferred Contact: [Email/Discord/GitHub Issues]

Availability:

  • Timezone: [Your Timezone]
  • Available for: Video call, pair debugging, testing beta fixes

Preferred Resolution Path

Option 1 (Ideal): Fix .copy() to support binary files

  • Would benefit entire E2B community
  • Aligns with standard Docker COPY behavior
  • Most maintainable long-term

Option 2: Provide alternative method for binary files

  • .addFile() or similar API
  • Clear documentation on usage
  • Acceptable if .copy() fix is not feasible

Option 3: Enhanced workaround documentation

  • Official guide for tarball installation
  • Best practices for private packages
  • Acceptable as interim solution

Gratitude

Thank you for E2B - it's a fantastic platform! We've successfully:

  • ✅ Built working templates with Claude Code
  • ✅ Integrated gcsfuse for GCS storage
  • ✅ Achieved < 2 second sandbox startup times
  • ✅ Deployed TypeScript development environments

This .copy() issue is the final blocker to production deployment. We appreciate any assistance you can provide!


Appendices

Appendix A: Complete Build Log

[Full build log with all steps and error messages]
[Available upon request - approximately 500 lines]

Appendix B: Tarball File Analysis

# File metadata
stat packages/test-pkg-1-1.0.0.tgz

# Archive contents
tar -tvzf packages/test-pkg-1-1.0.0.tgz

# Hex dump (first 256 bytes)
xxd -l 256 packages/test-pkg-1-1.0.0.tgz

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions