balenaCloud: Fixing the /v1/update 503 Error and Legacy Fleet Conversion Bug

Two bugs, one packet capture

Both issues are real, and at least one has a workaround right now. The 503 on /v1/update is a null-safety bug in the balena supervisor. The legacy-to-classic conversion has been broken for years, and the working path around it goes through Microservices, not Classic.

The 503 error: an empty POST body the supervisor can’t handle

The packet capture tells the story directly. The POST request from balena’s cloud to the supervisor carries content-length: 0 — no body. The supervisor’s /v1/update handler reads body.force to decide whether to override active update locks. With no body present, body is undefined, and JavaScript throws: Cannot read properties of undefined (reading ‘force’).

This is a missing null check. Something like req.body?.force ?? false would treat an absent body as force: false and carry on. Until that fix lands in the supervisor, the error keeps happening whenever the cloud sends a body-less POST to this endpoint.

If you’re calling /v1/update directly through the supervisor’s local HTTP API — rather than through the dashboard — there’s an immediate workaround: include a body in the request.

curl -X POST \
  -H 'Content-Type: application/json' \
  -d '{"force": false}' \
  'http://localhost:48484/v1/update?apikey=YOUR_KEY'

An empty JSON object ({}) may work in some supervisor versions too, since it gives the handler something to read without throwing. Use {"force": true} if you also need to bypass an active update lock.

When it’s the dashboard triggering the update — as in this packet capture, where the source IP belongs to balena’s cloud — the empty body is coming from balena’s own infrastructure. You can’t fix it from the device side. Either the API client should send a body, or the supervisor should default safely when none arrives. Either way it needs a fix upstream.

Why 503 instead of 500?

balena-supervisor has a documented habit of returning HTTP 503 for generic runtime errors that are not really service-unavailable situations. A JavaScript exception inside a route handler gets wrapped in a 503, which muddies diagnosis. The status code is the wrong signal; the JSON body is what matters for troubleshooting.

Legacy-to-classic conversion: why the button does nothing

balenaCloud organizes fleets into four types: Legacy, Classic, Essentials, and Microservices. Microservices supports multiple containers per device; Classic runs a single container; Legacy predates the multicontainer architecture. The conversion UI in fleet Settings is supposed to let you move up this hierarchy.

The Legacy to Classic path is broken. It has been for years. The UI shows the option, you click, and the device stays marked legacy. Given that Classic is itself a deprecated type — balena stopped allowing creation of new Classic fleets in 2023 — this particular conversion path is unlikely to ever get repaired.

The path that actually works is Legacy to Microservices. Every device in the fleet must be running balenaOS v2.12.0 or higher. Check each device’s OS version from the fleet dashboard. Anything below that threshold needs an OS update first — either via OTA if the device is still responsive, or by reflashing.

What if the hardware can’t reach balenaOS v2.12.0?

Some older boards have support only through earlier OS releases. If a device genuinely can’t be updated, the conversion will block or fail silently. The practical answer at that point: create a new Microservices fleet, move the devices that do meet the OS requirement, and retire the old legacy fleet. Hardware that can’t run v2.12.0 or later won’t convert regardless of what the dashboard shows.

Does fleet type affect the 503 bug?

No. The supervisor HTTP API behaves the same across all fleet types. The empty-body error hits legacy, classic, and microservices devices equally — it’s a supervisor-level bug, independent of fleet architecture.

Sources


Similar Posts