OpenHands / OpenHands/enterprise
DELETE /api/v1/sandboxes/{id}: path/handler param mismatch makes `sandbox_id` a required query param (and ignores the path)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 4
- Forks
- 2
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 101
Description
Summary
The DELETE /api/v1/sandboxes/{id} route's path parameter ({id}) does not match its handler's argument name (sandbox_id). Since FastAPI binds path parameters by name, {id} is never bound and sandbox_id is instead treated as a required query parameter. As a result:
DELETE /api/v1/sandboxes/<id>→ 422 Unprocessable Entity (sandbox_idquery field required)DELETE /api/v1/sandboxes/<id>?sandbox_id=<id>→200(the path segment is ignored; only the query value is used)
The sibling routes POST /{sandbox_id}/pause and POST /{sandbox_id}/resume are correct (their path placeholder matches the handler arg sandbox_id), so only delete_sandbox is affected.
Affected code
openhands/app_server/sandbox/sandbox_router.py (main @ 7b228db):
https://github.com/OpenHands/OpenHands/blob/7b228db6ae143598b4caf65c6f7ed759b511f922/openhands/app_server/sandbox/sandbox_router.py#L108-L116
@router.delete('/{id}', responses={404: {'description': 'Item not found'}})
async def delete_sandbox(
sandbox_id: str, # <-- arg name does not match the path param '{id}'
sandbox_service: SandboxService = sandbox_service_dependency,
) -> Success:
exists = await sandbox_service.delete_sandbox(sandbox_id)
if not exists:
raise HTTPException(status.HTTP_404_NOT_FOUND)
return Success()
Compare pause_sandbox / resume_sandbox immediately above, which use '/{sandbox_id}/...' and bind correctly.
Reproduction
$ curl -i -X DELETE http://localhost:3000/api/v1/sandboxes/oh-agent-server-XXXX
HTTP/1.1 422 Unprocessable Entity
{"detail":[{"type":"missing","loc":["query","sandbox_id"],"msg":"Field required","input":null}]}
$ curl -i -X DELETE "http://localhost:3000/api/v1/sandboxes/oh-agent-server-XXXX?sandbox_id=oh-agent-server-XXXX"
HTTP/1.1 200 OK
{"success":true}
Impact
- A REST client calling the route as documented (id in the path, per the OpenAPI schema and RESTful convention) gets a
422and cannot delete a sandbox without discovering the undocumented query param. - The generated OpenAPI schema is misleading: it advertises a
{id}path param that is ignored, plus a requiredsandbox_idquery param. - Because the path value is unused,
DELETE /sandboxes/A?sandbox_id=Bdeletes B, not A.
Suggested fix
Rename the path parameter to match the handler argument (consistent with the pause/resume routes):
@router.delete('/{sandbox_id}', responses={404: {'description': 'Item not found'}})
async def delete_sandbox(
sandbox_id: str,
...
(or, alternatively, rename the handler arg to id).
Environment
- openhands
1.8.0, self-hosted V1 app-server (docker.openhands.dev/openhands/openhands:latest), Docker runtime, observed onlocalhost:3000.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in openhands/app_server/sandbox/sandbox_router.py around lines 108-116 and compare delete_sandbox with the pause_sandbox and resume_sandbox routes. Reproduce the DELETE requests from the issue, then verify that the documented path parameter is bound and the path value, rather than a query parameter, selects the sandbox.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fastapi, python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100