GET /members returns its rows under a "workers" key, so a caller reading "members" sees an empty fleet #199

Closed
opened 2026-08-31 05:50:23 +02:00 by ltms · 1 comment
Owner

Found while writing the new 15 REST API Reference page.

What is wrong

The route was renamed /workers → /members in CB-557. The response body key was not renamed with it:

// FleetApp.java:322, in listMembers
body.put("workers", out);

So GET /members answers:

{ "workers": [ … ], "wipRefs": { … } }

Why it matters

The failure is silent, and silent in the worst direction. A caller that reads body["members"] — the obvious guess, given the path — gets an empty list, not an error and not a 404. An empty list is a completely valid answer here: it means "this fleet has no members right now". So the caller reports an idle fleet, and nothing anywhere says otherwise.

That is the same shape as the defect in #164: a lost answer and a real answer are indistinguishable to the caller.

Who is affected

  • fleet-manager reads body["workers"] (fleet_manager/probe.py:134) and is correct today. Any rename breaks it.
  • The MCP side is unaffected: fleet_list builds its own response and does not go through this handler.
  • The path itself is right; only the key disagrees with it.

Suggested direction

Do not just rename the key — that breaks fleet-manager silently, in exactly the same way, the moment someone deploys a new daemon against an old client.

Two options:

  1. Emit both keys for one release. Add members alongside workers, pointing at the same list. Update fleet-manager to read members with a fallback to workers. Drop workers a release later. This is the same deprecation-window shape used for the bridge_* → fleet_* tool rename.
  2. Leave it and document it. It is already documented on wiki page 15. This is a legitimate outcome — the cost of the mismatch is one line of documentation, and the cost of getting the migration wrong is a silently empty roster.

I lean towards (1), because "the path says members and the body says workers" is the kind of thing that will be rediscovered every few months by whoever next writes a client. But it is not urgent, and it must not be done as a one-line rename.

Not in scope

GET /sessions has the same shape ({"sessions": […]}) and is consistent with its path, so it needs nothing.

Found while writing the new [15 REST API Reference](https://git.ltms.dev/fleet/fleetd/wiki/15-REST-API-Reference) page. ## What is wrong The route was renamed `/workers` → `/members` in CB-557. The **response body key was not renamed with it**: ```java // FleetApp.java:322, in listMembers body.put("workers", out); ``` So `GET /members` answers: ```json { "workers": [ … ], "wipRefs": { … } } ``` ## Why it matters The failure is silent, and silent in the worst direction. A caller that reads `body["members"]` — the obvious guess, given the path — gets an **empty list**, not an error and not a 404. An empty list is a completely valid answer here: it means "this fleet has no members right now". So the caller reports an idle fleet, and nothing anywhere says otherwise. That is the same shape as the defect in #164: a lost answer and a real answer are indistinguishable to the caller. ## Who is affected - `fleet-manager` reads `body["workers"]` (`fleet_manager/probe.py:134`) and is **correct today**. Any rename breaks it. - The MCP side is unaffected: `fleet_list` builds its own response and does not go through this handler. - The path itself is right; only the key disagrees with it. ## Suggested direction Do **not** just rename the key — that breaks `fleet-manager` silently, in exactly the same way, the moment someone deploys a new daemon against an old client. Two options: 1. **Emit both keys for one release.** Add `members` alongside `workers`, pointing at the same list. Update `fleet-manager` to read `members` with a fallback to `workers`. Drop `workers` a release later. This is the same deprecation-window shape used for the `bridge_*` → `fleet_*` tool rename. 2. **Leave it and document it.** It is already documented on wiki page 15. This is a legitimate outcome — the cost of the mismatch is one line of documentation, and the cost of getting the migration wrong is a silently empty roster. I lean towards (1), because "the path says members and the body says workers" is the kind of thing that will be rediscovered every few months by whoever next writes a client. But it is not urgent, and it must not be done as a one-line rename. ## Not in scope `GET /sessions` has the same shape (`{"sessions": […]}`) and is consistent with its path, so it needs nothing.
Author
Owner

Fixed on main in a1a9015. Full suite: 1065 tests, 0 failures.

GET /members now returns its rows under members. workers stays as a deprecated alias carrying the same rows, because the REST surface is the out-of-band path a lead falls back to when its MCP mount drops, and silently breaking that would trade one invisible failure for another. The alias can go once nothing reads it.

The test now asserts both keys and that they are equal, so the alias cannot drift from the canonical key without failing. Watched failing without the fix:

GET /members must return its rows under "members" ==> expected: not <null>

One thing this does not fix, noted rather than changed: docs/Worker-Startup-and-Trust.md still refers to POST /workers, which is a stale endpoint name from before the CB-634 rename.

Fixed on `main` in `a1a9015`. Full suite: 1065 tests, 0 failures. `GET /members` now returns its rows under `members`. `workers` stays as a deprecated alias carrying the same rows, because the REST surface is the out-of-band path a lead falls back to when its MCP mount drops, and silently breaking that would trade one invisible failure for another. The alias can go once nothing reads it. The test now asserts both keys and that they are equal, so the alias cannot drift from the canonical key without failing. Watched failing without the fix: ``` GET /members must return its rows under "members" ==> expected: not <null> ``` One thing this does not fix, noted rather than changed: `docs/Worker-Startup-and-Trust.md` still refers to `POST /workers`, which is a stale endpoint name from before the CB-634 rename.
ltms closed this issue 2026-08-31 17:20:24 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#199