Skip to content

sandbox pause/resume fail with "Content-Length is required" (HTTP 411) #77

Description

@pratikbin

Summary

createos sandbox pause and createos sandbox resume fail on every sandbox, addressed by name or by ID. The API returns HTTP 411 Length Required.

$ createos sandbox pause bench-2
  ERROR  Content-Length is required
  Hint: Check that the value you provided is correct and try again.

Root cause

lifecyclePOST() in internal/api/sandbox.go builds the request with no .SetBody(...):

func (c *SandboxClient) lifecyclePOST(ctx context.Context, id, path string) (*SandboxView, error) {
	var envelope Response[SandboxView]
	resp, err := c.Client.R().
		SetContext(ctx).
		SetPathParam("id", id).
		SetResult(&envelope).
		Post(path)
	...

Both PauseSandbox and ResumeSandbox call this helper, so both endpoints fail.

Over HTTP/2, a Go client request with no body sends no Content-Length header — it signals end of body with END_STREAM instead, which is valid HTTP/2. The API requires Content-Length on every POST and rejects the request with 411 before it reaches route logic. Other lifecycle calls that already set a body (CreateSandbox, ForkSandbox, AttachDisk, …) are unaffected.

Repro

$ createos --debug sandbox pause sb-01m0vyh91ayt2sardrn3b0vz8q
~~~ REQUEST ~~~
POST  /v1/sandboxes/sb-01m0vyh91ayt2sardrn3b0vz8q/pause  HTTP/1.1
BODY   :
***** NO CONTENT *****
------------------------------------------------------------------------------
~~~ RESPONSE ~~~
STATUS       : 411 Length Required
BODY         :
{
   "status": "fail",
   "data": "Content-Length is required"
}

Identical failure for resume (POST /v1/sandboxes/{id}/resume).

Fix (verified against the live API)

Give the request a body:

resp, err := c.Client.R().
    SetContext(ctx).
    SetPathParam("id", id).
    SetBody(map[string]any{}).
    SetResult(&envelope).
    Post(path)

Built and ran this exact change locally against api.sb.createos.sh:

  • pause returned 200 OK, sandbox reached paused.
  • resume returned 200 OK with status:"resuming", and the sandbox settled into running.

Version

Version: dev
Channel: stable
Commit:  unknown

Built from main at b6f98bb ("fix(sandbox): pin ssh known_hosts to sandbox id (#76)").

Also noticed while restoring test state (separate, lower confidence)

After the fix, one resume briefly read back status:"paused" on the first poll or two before settling into resumingrunning within ~11s. Might be read-path eventual consistency rather than a real regression — did not chase further, flagging in case it's already on your radar.

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