Greenfield manifest-first bootstrap is a chicken-and-egg: apply demands a store, capture refuses an absent project #104
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#104
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?
Found by the 2026-07-19 release drill (two live Coolify 4.1.2 instances).
What happened
Fresh instance, registered project, manifest with databases only and zero
${…}refs. There is no path to the first apply:cast apply→no secret store for dan-claude-bot/drill-widget in drill(src/cli.ts:1160-1172— unconditionalexistsSync(store)check)cast capture→ refuses correctly: the project is absent on the box, and capture "may only ever describe one that is already there"The documented two-pass bootstrap (README The bootstrap is two-pass) starts from
capture— which presumes live resources to describe, i.e. the adoption path. A greenfield environment (nothing on the box, everything declared in the manifest) satisfies neither verb's precondition.The drill unblocked by hand-writing an empty store:
…after which apply → diff → re-apply all behaved perfectly. But that line appears in no doc and no error message.
Why it matters
This is the first command a manifest-first adopter runs, and it is also the DR shape ("rebuild the control plane from state" with a store that never existed for a new project). The refusal message says
cast capturewrites the store — which then refuses in turn, one message deep from the answer.Options (in rough order of smallness)
no secret storerefusal gains the empty-store one-liner for the zero-refs case.${…}refs, treat an absent store as empty (say so loudly) and proceed — the fleet-scale silent-skip concern doesn't apply, because nothing needed resolving.cast init-store <org>/<repo> --env <env>writing the empty store to the bound recipient.Option 2 keeps the file requirement meaningful exactly when it protects something. Happy to take direction.
🤖 Filed from the release-drill session.