compose_file without a leading slash 422s the create — cast validates nothing, and its own docs teach the broken value #49
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#49
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What happens
A first apply, planning
create application core, got a bare 422 from Coolify. The manifest said:Coolify 4.1.2 validates
docker_compose_locationon create and rejects a path without a leading slash. The correct value is/docker-compose.yaml. By the time the 422 lands,applyhas already created the project and the environment — so a pre-flightable, deterministic, manifest-shaped error is being paid for with a half-applied run.Root cause
cast passes the field through verbatim and validates nothing about its shape:
manifest.ts:25requirescompose_fileto be present on adockercomposeapp, and nothing else. It is az.string().resolve.ts:361sends it asdocker_compose_location: app.build.compose_file.Meanwhile cast's own draft writer already knows the right shape —
draft.ts:459defaults the field to"/docker-compose.yaml", leading slash included, because that is what it reads off a live box.So cast writes the correct value when it generates a manifest, and accepts the incorrect one when it consumes one.
The part that makes this worth a hard validation rather than a note
docs/semantics.md:85teaches the broken value. The documented example of a compose app is:That is the exact string Coolify 422s on create. Anyone hand-writing a manifest from the docs writes a manifest that cannot be applied to a fresh box — and will only find out mid-apply, after the project exists.
Why it went unnoticed for so long
Only a create can surface it.
diffhappily reports the field, andapplynever re-validates an existing application — so the live founder-facing box, hand-built with/docker-compose.yaml, worked for months while the manifest carried a value that had never been sent to a create. The difference between the two even sat on a reviewed drift list, dispositioned as "expected — cosmetic". (Written up as D-265 in heavy-duty/incubator#10; fixed on the manifest side in heavy-duty/incubator#9.)Suggested
Unlike the multi-destination 400 (#41) and the domain 409 (#44), this one is fully knowable before a single API call is made — it is a property of the manifest, not of the instance. So it should not be a translated error; it should be a refusal at parse time, at zero cost, on every verb:
manifest.ts— refinecompose_fileto require a leading/, with a message that names the fix:A refine, not a silent normalization: cast should not quietly rewrite what the manifest says. The manifest is the artifact under review, and a value that would 422 should be fixed in the file, in a commit, once — not repaired in memory on every run.
docs/semantics.md:85— fix the example. It is the source of the bug.Worth a look while in there:
base_directoryis passed through the same way. If Coolify has the same expectation of it, the same refinement applies.