The multi-destination 400 is unactionable, and arrives after apply has already mutated Coolify #41

Closed
opened 2026-07-14 16:50:27 +00:00 by dan-claude-bot · 0 comments
dan-claude-bot commented 2026-07-14 16:50:27 +00:00 (Migrated from github.com)

What happens

A first apply against a server with more than one destination gets this, from Coolify, on the first resource create:

5 change(s), 0 orphan(s)
POST /applications/private-github-app → 400: {"message":"Server has multiple destinations and you do not set destination_uuid."}

By then cast has already created the project and the environment. The run is half-applied, and the message names neither the fix nor the file it goes in.

Why cast cannot pre-flight it (and what it can do instead)

Coolify 4.1.2 serves no destinations API — not list, not read, not create. GET /servers/{uuid} does not expose them either (the Server schema has no destination field). So cast genuinely cannot know a server's destination count before attempting a create. That is not fixable here.

What is fixable is the diagnosis. Two things:

1. Translate the 400. Catch this specific Coolify message and re-raise it as something that names the remedy:

<server> has multiple destinations, so a create must say which one to use.
Read the destination UUID from the Coolify UI (4.1.2 exposes no API for it) and declare it as:

environments.<env>.projects.<org>/<repo>.destination_uuid

Placement is create-time — a resource cannot be moved between networks later.

2. Make the assumption visible in the plan. Today an undeclared destination_uuid is silent, and cast's own source calls it "the server's only destination, which is what Coolify picks anyway". That inference is invisible until it is wrong. When no destination_uuid is declared, the plan should say so out loud — one line, e.g. placement: server's default destination (none declared) — so the assumption is on screen at the moment it is being made rather than in a comment.

Why this bites harder than a normal 400

Placement is create-time: apply never moves a resource between networks, so recovering from a wrong or missing destination means delete + recreate. And the failure mode is asymmetric in a way that hides itself — a single-destination server ignores destination_uuid entirely and never validates it (so a wrong value is invisible), while a multi-destination server hard-400s any create that omits it. The moment a server gains its second destination, every project on it that never declared one stops being able to deploy.

## What happens A first apply against a server with more than one destination gets this, from Coolify, on the first resource create: ``` 5 change(s), 0 orphan(s) POST /applications/private-github-app → 400: {"message":"Server has multiple destinations and you do not set destination_uuid."} ``` By then cast has already created the project and the environment. The run is half-applied, and the message names neither the fix nor the file it goes in. ## Why cast cannot pre-flight it (and what it can do instead) Coolify 4.1.2 serves **no destinations API** — not list, not read, not create. `GET /servers/{uuid}` does not expose them either (the `Server` schema has no destination field). So cast genuinely **cannot** know a server's destination count before attempting a create. That is not fixable here. What is fixable is the diagnosis. Two things: **1. Translate the 400.** Catch this specific Coolify message and re-raise it as something that names the remedy: > `<server>` has multiple destinations, so a create must say which one to use. > Read the destination UUID from the Coolify UI (4.1.2 exposes no API for it) and declare it as: > > environments.<env>.projects.<org>/<repo>.destination_uuid > > Placement is create-time — a resource cannot be moved between networks later. **2. Make the assumption visible in the plan.** Today an undeclared `destination_uuid` is silent, and cast's own source calls it *"the server's only destination, which is what Coolify picks anyway"*. That inference is invisible until it is wrong. When no `destination_uuid` is declared, the plan should say so out loud — one line, e.g. `placement: server's default destination (none declared)` — so the assumption is on screen at the moment it is being made rather than in a comment. ## Why this bites harder than a normal 400 Placement is **create-time**: apply never moves a resource between networks, so recovering from a wrong or missing destination means delete + recreate. And the failure mode is asymmetric in a way that hides itself — a **single**-destination server *ignores* `destination_uuid` entirely and never validates it (so a wrong value is invisible), while a **multi**-destination server hard-400s any create that omits it. The moment a server gains its second destination, every project on it that never declared one stops being able to deploy.
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: heavy-duty/cast#41
No description provided.