[GH-ISSUE #443] Sandbox endpoints return 404 instead of 400 for malformed sandbox IDs #111

Closed
opened 2026-10-29 14:19:41 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @murtuzamvista on GitHub (Oct 27, 2026).
Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/443

Affected component

API server / proxy

AgentENV version

  • Version: aenv 0.2.4
  • commit: 424b852d96
  • Local modifications: none

Follow-up to #5 (E2B API Compatibility Matrix).

Problem

The matrix notes that E2B returns 400 for an invalid sandbox ID. AgentENV currently returns 404 with sandbox <id> not found for a malformed ID, the same as for a well-formed ID that doesn't exist.

Reproduction

Tested on a local server (Ubuntu 24.04 arm64, installed with install.sh):

curl -s -w "\n%{http_code}\n" -H "X-API-Key: $E2B_API_KEY" \
  http://127.0.0.1:8000/sandboxes/not-a-valid-id
# {"code":404,"message":"sandbox not-a-valid-id not found"}
# 404

The same 404 comes back from POST /sandboxes/not-a-valid-id/{pause,fork,connect,timeout} (the last two with a valid body).

For comparison, a well-formed UUID that doesn't exist:

curl -s -w "\n%{http_code}\n" -H "X-API-Key: $E2B_API_KEY" \
  http://127.0.0.1:8000/sandboxes/01a1771b-0000-7000-8000-000000000000
# {"code":404,"message":"sandbox 01a1771b-0000-7000-8000-000000000000 not found"}
# 404

Expected

Path ID Status
Malformed (not parseable as a sandbox ID) 400, with the same {code, message} body shape
Well-formed but nonexistent 404 (unchanged)

Proposal

Validate the ID at the shared lookup point. If it doesn't parse as a UUID, return 400 with a clear message. Add tests for both cases.

Open question

Sandbox IDs here are UUIDv7, while E2B's own IDs have a different format. Is "doesn't parse as a UUID" the right definition of invalid, or would you prefer something else?

I'd like to work on this. Happy to wait for a view on the question above before opening a PR.

Host environment

  • OS: Ubuntu 24.04.5 LTS (noble)
  • Kernel (uname -a): Linux lima-aenv 6.8.0-142-generic #142-Ubuntu SMP PREEMPT_DYNAMIC Wed Sep 2 14:30:29 UTC 2026 aarch64 aarch64 aarch64 GNU/Linux
  • Architecture: aarch64 (arm64)
  • Bare metal, VM, or cloud: VM. Lima with vmType vz (Apple Virtualization.framework, systemd-detect-virt reports "apple"), nested virtualization enabled, on a MacBook Pro with an Apple M3 Pro, macOS 26.6.2. /dev/kvm is present in the guest.
  • ublk support: yes. ublk_drv is not in the base kernel package; it loads after installing linux-modules-extra-6.8.0-142-generic (modprobe ublk_drv succeeds, module shows in lsmod)
  • CPU: 6 vCPUs on an Apple M3 Pro host (the guest reports no model name)
  • Memory: 9.7 GiB visible to the VM (host has 18 GB), no swap
  • Storage/filesystem: ext4 on /dev/vda1, 77 GB (73 GB free)

Operation and configuration


Server: installed with `install.sh`, running as the `aenv` systemd service, listening on `127.0.0.1:8000`. Default configuration, nothing customised.

Client environment (API key redacted):


export E2B_API_URL=http://127.0.0.1:8000
export E2B_SANDBOX_URL=http://127.0.0.1:8000
export E2B_API_KEY=<redacted>


HTTP requests:


API=http://127.0.0.1:8000
H=(-H "X-API-Key: $E2B_API_KEY" -H "Content-Type: application/json")

# Operation: GET /sandboxes/{sandboxID} (malformed ID)
curl -s -w "\n%{http_code}\n" "${H[@]}" $API/sandboxes/not-a-valid-id

# Same ID on the other sandbox operations
curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/pause
curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/fork -d '{}'
curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/connect -d '{"timeout":300}'
curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/timeout -d '{"timeout":300}'

# Control case: well-formed UUID that does not exist
curl -s -w "\n%{http_code}\n" "${H[@]}" $API/sandboxes/01a1771b-0000-7000-8000-000000000000


Observed responses (same order as the requests above):

| Request | Status | Body |
|---|---|---|
| `GET /sandboxes/not-a-valid-id` | 404 | `{"code":404,"message":"sandbox not-a-valid-id not found"}` |
| `POST /sandboxes/not-a-valid-id/pause` | 404 | same |
| `POST /sandboxes/not-a-valid-id/fork` | 404 | same |
| `POST /sandboxes/not-a-valid-id/connect` | 404 | same |
| `POST /sandboxes/not-a-valid-id/timeout` | 404 | same |
| `GET /sandboxes/01a1771b-0000-7000-8000-000000000000` (well-formed, nonexistent) | 404 | `{"code":404,"message":"sandbox 01a1771b-0000-7000-8000-000000000000 not found"}` |

Expected: the five malformed-ID requests return `400`, and only the last one returns `404`.

Steps to reproduce

  1. Install and start the server on a Linux host with /dev/kvm (tested on Ubuntu 24.04 arm64; the behavior doesn't depend on the architecture as far as I can tell):

    curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/install.sh -o install.sh
    sudo bash install.sh
    sudo systemctl start aenv
    curl -i http://127.0.0.1:8000/health      # expect 204
    
  2. Read the generated API key (redact it in anything you share):

    export E2B_API_KEY="$(sudo cat /var/lib/aenv/secrets/api-key)"
    API=http://127.0.0.1:8000
    
  3. Request a sandbox with a malformed ID. No sandbox needs to exist:

    curl -s -w "\n%{http_code}\n" -H "X-API-Key: $E2B_API_KEY" $API/sandboxes/not-a-valid-id
    

    Actual: {"code":404,"message":"sandbox not-a-valid-id not found"} / 404
    Expected: 400

  4. Repeat with the other sandbox operations (same actual result, 404):

    H=(-H "X-API-Key: $E2B_API_KEY" -H "Content-Type: application/json")
    curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/pause
    curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/fork -d '{}'
    curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/connect -d '{"timeout":300}'
    curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/timeout -d '{"timeout":300}'
    
  5. Control case: a well-formed UUID that doesn't exist. 404 is correct here:

    curl -s -w "\n%{http_code}\n" -H "X-API-Key: $E2B_API_KEY" \
      $API/sandboxes/01a1771b-0000-7000-8000-000000000000
    

    Actual: {"code":404,"message":"sandbox 01a1771b-0000-7000-8000-000000000000 not found"} / 404

Expected behavior

A sandbox ID that isn't well-formed should be rejected as a bad request, not reported as a missing sandbox. The E2B API Compatibility Matrix in #5 notes that E2B returns 400 for an invalid sandbox ID, and AgentENV aims to be compatible with E2B clients.

Request Expected status Currently
GET /sandboxes/not-a-valid-id 400 404
POST /sandboxes/not-a-valid-id/pause 400 404
POST /sandboxes/not-a-valid-id/fork 400 404
POST /sandboxes/not-a-valid-id/connect 400 404
POST /sandboxes/not-a-valid-id/timeout 400 404
GET /sandboxes/01a1771b-0000-7000-8000-000000000000 (well-formed, nonexistent) 404 (unchanged) 404

The error body should keep the existing shape, {"code": 400, "message": "..."}, with a message that says the ID is invalid (for example, invalid sandbox ID: not-a-valid-id), so clients can tell a malformed ID from a missing sandbox.

Open question: sandbox IDs in AgentENV are UUIDv7, while E2B's own IDs have a different format. My proposal is to treat any path ID that doesn't parse as a UUID as invalid. Happy to adjust if you'd define it differently.

Actual behavior

Observed responses (same order as the requests above):

Request Status Body
GET /sandboxes/not-a-valid-id 404 {"code":404,"message":"sandbox not-a-valid-id not found"}
POST /sandboxes/not-a-valid-id/pause 404 same
POST /sandboxes/not-a-valid-id/fork 404 same
POST /sandboxes/not-a-valid-id/connect 404 same
POST /sandboxes/not-a-valid-id/timeout 404 same
GET /sandboxes/01a1771b-0000-7000-8000-000000000000 (well-formed, nonexistent) 404 {"code":404,"message":"sandbox 01a1771b-0000-7000-8000-000000000000 not found"}

Expected: the five malformed-ID requests return 400, and only the last one returns 404.

Logs and diagnostics

**Logs and diagnostics**

Server log, collected with `journalctl -u aenv --since "<time of the requests>" --no-pager -o short-iso` while running the requests from the reproduction steps: **no entries** at the default log level (no errors, warnings or backtraces). The only observable behavior is the HTTP response shown above.

ublk-daemon, Firecracker and scheduler logs are not relevant here. No sandbox is created or touched by these requests, and no scheduler is running (single-node install).

If a higher log level would help, tell me which setting to use and I'll attach the output.

Reproduction frequency

Always

Regression information

Not known to be a regression. I have only tested one build, the one installed by install.sh (version/commit: ), and have no earlier build to compare against. The behavior may have always been this way.

Additional context

  • Related: #5 (E2B API Compatibility Matrix), where this gap is listed under sandbox lifecycle. A maintainer suggested tracking it separately.
  • Scope: all five sandbox operations I tested return the identical 404 message for a malformed ID, so the check may belong in one shared lookup. I haven't read the code yet, so this is a guess from the behavior.
  • Tested with plain curl. I didn't test whether any E2B SDK treats the 400 and 404 differently on the client side.
  • Not part of this issue, noticed while testing: POST /sandboxes/{id}/connect and /timeout with a body missing timeout return 422. I believe E2B returns 400 for malformed request bodies, but I haven't verified that against the E2B spec. I can open a separate issue if that's useful.
  • I'm happy to implement this and add tests once the definition of an invalid ID is settled.

Pre-submission checklist

  • I searched existing open and closed issues and did not find a duplicate.
  • I provided a minimal reproduction or explained why one cannot be provided.
  • I removed credentials, tokens, private image references, and other sensitive information.
  • This is not a security vulnerability.
Originally created by @murtuzamvista on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/443 ### Affected component API server / proxy ### AgentENV version - Version: aenv 0.2.4 - commit: 424b852d961ff26d75ab609fa499dae071713528 - Local modifications: none Follow-up to #5 (E2B API Compatibility Matrix). ## Problem The matrix notes that E2B returns `400` for an invalid sandbox ID. AgentENV currently returns `404` with `sandbox <id> not found` for a malformed ID, the same as for a well-formed ID that doesn't exist. ## Reproduction Tested on a local server (Ubuntu 24.04 arm64, installed with `install.sh`): ```bash curl -s -w "\n%{http_code}\n" -H "X-API-Key: $E2B_API_KEY" \ http://127.0.0.1:8000/sandboxes/not-a-valid-id # {"code":404,"message":"sandbox not-a-valid-id not found"} # 404 ``` The same 404 comes back from `POST /sandboxes/not-a-valid-id/{pause,fork,connect,timeout}` (the last two with a valid body). For comparison, a well-formed UUID that doesn't exist: ```bash curl -s -w "\n%{http_code}\n" -H "X-API-Key: $E2B_API_KEY" \ http://127.0.0.1:8000/sandboxes/01a1771b-0000-7000-8000-000000000000 # {"code":404,"message":"sandbox 01a1771b-0000-7000-8000-000000000000 not found"} # 404 ``` ## Expected | Path ID | Status | |---|---| | Malformed (not parseable as a sandbox ID) | `400`, with the same `{code, message}` body shape | | Well-formed but nonexistent | `404` (unchanged) | ## Proposal Validate the ID at the shared lookup point. If it doesn't parse as a UUID, return 400 with a clear message. Add tests for both cases. ## Open question Sandbox IDs here are UUIDv7, while E2B's own IDs have a different format. Is "doesn't parse as a UUID" the right definition of invalid, or would you prefer something else? I'd like to work on this. Happy to wait for a view on the question above before opening a PR. ### Host environment - OS: Ubuntu 24.04.5 LTS (noble) - Kernel (`uname -a`): Linux lima-aenv 6.8.0-142-generic #142-Ubuntu SMP PREEMPT_DYNAMIC Wed Sep 2 14:30:29 UTC 2026 aarch64 aarch64 aarch64 GNU/Linux - Architecture: aarch64 (arm64) - Bare metal, VM, or cloud: VM. Lima with vmType vz (Apple Virtualization.framework, systemd-detect-virt reports "apple"), nested virtualization enabled, on a MacBook Pro with an Apple M3 Pro, macOS 26.6.2. /dev/kvm is present in the guest. - ublk support: yes. ublk_drv is not in the base kernel package; it loads after installing linux-modules-extra-6.8.0-142-generic (`modprobe ublk_drv` succeeds, module shows in lsmod) - CPU: 6 vCPUs on an Apple M3 Pro host (the guest reports no model name) - Memory: 9.7 GiB visible to the VM (host has 18 GB), no swap - Storage/filesystem: ext4 on /dev/vda1, 77 GB (73 GB free) ### Operation and configuration ```shell Server: installed with `install.sh`, running as the `aenv` systemd service, listening on `127.0.0.1:8000`. Default configuration, nothing customised. Client environment (API key redacted): export E2B_API_URL=http://127.0.0.1:8000 export E2B_SANDBOX_URL=http://127.0.0.1:8000 export E2B_API_KEY=<redacted> HTTP requests: API=http://127.0.0.1:8000 H=(-H "X-API-Key: $E2B_API_KEY" -H "Content-Type: application/json") # Operation: GET /sandboxes/{sandboxID} (malformed ID) curl -s -w "\n%{http_code}\n" "${H[@]}" $API/sandboxes/not-a-valid-id # Same ID on the other sandbox operations curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/pause curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/fork -d '{}' curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/connect -d '{"timeout":300}' curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/timeout -d '{"timeout":300}' # Control case: well-formed UUID that does not exist curl -s -w "\n%{http_code}\n" "${H[@]}" $API/sandboxes/01a1771b-0000-7000-8000-000000000000 Observed responses (same order as the requests above): | Request | Status | Body | |---|---|---| | `GET /sandboxes/not-a-valid-id` | 404 | `{"code":404,"message":"sandbox not-a-valid-id not found"}` | | `POST /sandboxes/not-a-valid-id/pause` | 404 | same | | `POST /sandboxes/not-a-valid-id/fork` | 404 | same | | `POST /sandboxes/not-a-valid-id/connect` | 404 | same | | `POST /sandboxes/not-a-valid-id/timeout` | 404 | same | | `GET /sandboxes/01a1771b-0000-7000-8000-000000000000` (well-formed, nonexistent) | 404 | `{"code":404,"message":"sandbox 01a1771b-0000-7000-8000-000000000000 not found"}` | Expected: the five malformed-ID requests return `400`, and only the last one returns `404`. ``` ### Steps to reproduce 1. Install and start the server on a Linux host with `/dev/kvm` (tested on Ubuntu 24.04 arm64; the behavior doesn't depend on the architecture as far as I can tell): ```bash curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/install.sh -o install.sh sudo bash install.sh sudo systemctl start aenv curl -i http://127.0.0.1:8000/health # expect 204 ``` 2. Read the generated API key (redact it in anything you share): ```bash export E2B_API_KEY="$(sudo cat /var/lib/aenv/secrets/api-key)" API=http://127.0.0.1:8000 ``` 3. Request a sandbox with a malformed ID. No sandbox needs to exist: ```bash curl -s -w "\n%{http_code}\n" -H "X-API-Key: $E2B_API_KEY" $API/sandboxes/not-a-valid-id ``` Actual: `{"code":404,"message":"sandbox not-a-valid-id not found"}` / `404` Expected: `400` 4. Repeat with the other sandbox operations (same actual result, `404`): ```bash H=(-H "X-API-Key: $E2B_API_KEY" -H "Content-Type: application/json") curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/pause curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/fork -d '{}' curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/connect -d '{"timeout":300}' curl -s -w "\n%{http_code}\n" "${H[@]}" -X POST $API/sandboxes/not-a-valid-id/timeout -d '{"timeout":300}' ``` 5. Control case: a well-formed UUID that doesn't exist. `404` is correct here: ```bash curl -s -w "\n%{http_code}\n" -H "X-API-Key: $E2B_API_KEY" \ $API/sandboxes/01a1771b-0000-7000-8000-000000000000 ``` Actual: `{"code":404,"message":"sandbox 01a1771b-0000-7000-8000-000000000000 not found"}` / `404` ### Expected behavior A sandbox ID that isn't well-formed should be rejected as a bad request, not reported as a missing sandbox. The E2B API Compatibility Matrix in #5 notes that E2B returns `400` for an invalid sandbox ID, and AgentENV aims to be compatible with E2B clients. | Request | Expected status | Currently | |---|---|---| | `GET /sandboxes/not-a-valid-id` | `400` | `404` | | `POST /sandboxes/not-a-valid-id/pause` | `400` | `404` | | `POST /sandboxes/not-a-valid-id/fork` | `400` | `404` | | `POST /sandboxes/not-a-valid-id/connect` | `400` | `404` | | `POST /sandboxes/not-a-valid-id/timeout` | `400` | `404` | | `GET /sandboxes/01a1771b-0000-7000-8000-000000000000` (well-formed, nonexistent) | `404` (unchanged) | `404` | The error body should keep the existing shape, `{"code": 400, "message": "..."}`, with a message that says the ID is invalid (for example, `invalid sandbox ID: not-a-valid-id`), so clients can tell a malformed ID from a missing sandbox. Open question: sandbox IDs in AgentENV are UUIDv7, while E2B's own IDs have a different format. My proposal is to treat any path ID that doesn't parse as a UUID as invalid. Happy to adjust if you'd define it differently. ### Actual behavior Observed responses (same order as the requests above): | Request | Status | Body | |---|---|---| | `GET /sandboxes/not-a-valid-id` | 404 | `{"code":404,"message":"sandbox not-a-valid-id not found"}` | | `POST /sandboxes/not-a-valid-id/pause` | 404 | same | | `POST /sandboxes/not-a-valid-id/fork` | 404 | same | | `POST /sandboxes/not-a-valid-id/connect` | 404 | same | | `POST /sandboxes/not-a-valid-id/timeout` | 404 | same | | `GET /sandboxes/01a1771b-0000-7000-8000-000000000000` (well-formed, nonexistent) | 404 | `{"code":404,"message":"sandbox 01a1771b-0000-7000-8000-000000000000 not found"}` | Expected: the five malformed-ID requests return `400`, and only the last one returns `404`. ### Logs and diagnostics ```text **Logs and diagnostics** Server log, collected with `journalctl -u aenv --since "<time of the requests>" --no-pager -o short-iso` while running the requests from the reproduction steps: **no entries** at the default log level (no errors, warnings or backtraces). The only observable behavior is the HTTP response shown above. ublk-daemon, Firecracker and scheduler logs are not relevant here. No sandbox is created or touched by these requests, and no scheduler is running (single-node install). If a higher log level would help, tell me which setting to use and I'll attach the output. ``` ### Reproduction frequency Always ### Regression information Not known to be a regression. I have only tested one build, the one installed by `install.sh` (version/commit: <fill in from the commands below>), and have no earlier build to compare against. The behavior may have always been this way. ### Additional context - Related: #5 (E2B API Compatibility Matrix), where this gap is listed under sandbox lifecycle. A maintainer suggested tracking it separately. - Scope: all five sandbox operations I tested return the identical 404 message for a malformed ID, so the check may belong in one shared lookup. I haven't read the code yet, so this is a guess from the behavior. - Tested with plain `curl`. I didn't test whether any E2B SDK treats the 400 and 404 differently on the client side. - Not part of this issue, noticed while testing: `POST /sandboxes/{id}/connect` and `/timeout` with a body missing `timeout` return `422`. I believe E2B returns `400` for malformed request bodies, but I haven't verified that against the E2B spec. I can open a separate issue if that's useful. - I'm happy to implement this and add tests once the definition of an invalid ID is settled. ### Pre-submission checklist - [x] I searched existing open and closed issues and did not find a duplicate. - [x] I provided a minimal reproduction or explained why one cannot be provided. - [x] I removed credentials, tokens, private image references, and other sensitive information. - [x] This is not a security vulnerability.
bsakaguchi 2026-10-29 14:19:41 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@yingdi-shan commented on GitHub (Oct 28, 2026):

Thanks for the detailed report.

We'd like to keep 404 here. From the client's side, a malformed ID and a well-formed ID that doesn't exist mean the same thing: there's no sandbox with that ID. 404 sandbox not found is accurate in both cases, and clients only need one error path.

Returning 400 would mean the API has to know what a valid ID looks like, so the UUID format becomes part of the public contract. That's an internal detail we'd rather not commit to, and we may want to change the ID scheme later. So we'll close this as intended behavior.

<!-- gh-comment-id:6458104877 --> @yingdi-shan commented on GitHub (Oct 28, 2026): Thanks for the detailed report. We'd like to keep 404 here. From the client's side, a malformed ID and a well-formed ID that doesn't exist mean the same thing: there's no sandbox with that ID. 404 sandbox <id> not found is accurate in both cases, and clients only need one error path. Returning 400 would mean the API has to know what a valid ID looks like, so the UUID format becomes part of the public contract. That's an internal detail we'd rather not commit to, and we may want to change the ID scheme later. So we'll close this as intended behavior.
Author
Owner

@murtuzamvista commented on GitHub (Oct 28, 2026):

Thanks, that makes sense: the ID format staying an internal detail is a good reason. Understood on keeping 404. I’ll look at other items instead.

<!-- gh-comment-id:6462169030 --> @murtuzamvista commented on GitHub (Oct 28, 2026): Thanks, that makes sense: the ID format staying an internal detail is a good reason. Understood on keeping 404. I’ll look at other items instead.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kvcache-ai/AgentENV#111
No description provided.