From 93bca378908803c8a706ba7548a3eef446e731b4 Mon Sep 17 00:00:00 2001 From: claude-hdb Date: Tue, 14 Jul 2026 22:24:10 +0000 Subject: [PATCH] feat(resolve): warn that apply cannot enable "Include Source Commit in Build" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coolify 4.1.2 gates the SOURCE_COMMIT *build arg* behind a per-application setting (ApplicationSetting.include_source_commit_in_build, default false) that has no API surface: it appears in zero API controllers, and both the create (l.914) and PATCH (l.2368) allowlists in ApplicationsController.php reject unrecognized keys outright ("This field is not allowed."), so it cannot be smuggled through — sending it would 422 the whole request. Its only writer in v4.1.2 is the Livewire Advanced tab (Advanced.php:128), i.e. a human in the UI. So apply says it out loud, once per dockercompose application, via the same desiredFromManifest mechanism and in the same voice as the existing umami service-domains warning. A manual step the tool knows about and does not mention is a manual step that gets forgotten — and this one fails green. Note the toggle gates the BUILD-time arg only; Coolify's runtime injection of SOURCE_COMMIT is unconditional (ApplicationDeploymentJob.php:2949), so a service reading process.env.SOURCE_COMMIT per request never needed it. The warning therefore does not repeat #46's original (incorrect) premise that this toggle is why /version reported sha "unknown" — the real cause is an app-level env var suppressing Coolify's own injection (#50). Closes #46. Co-Authored-By: Claude Opus 4.8 --- docs/semantics.md | 27 +++++++++++++++++++++++ src/resolve.ts | 28 ++++++++++++++++++++++++ test/resolve.test.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/docs/semantics.md b/docs/semantics.md index 58cb803..51b55ba 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -624,6 +624,33 @@ one used — verified against a live private clone. Coolify 4.1.2 services — configure hostnames manually in the Coolify UI`) once per run for every service that declared any. Set service hostnames in the Coolify UI by hand. +- **"Include Source Commit in Build" cannot be enabled via the API in Coolify + 4.1.2 — `apply` warns instead.** A dockercompose application whose build + consumes `SOURCE_COMMIT` as a **build arg** only receives it if the + per-application setting *Include Source Commit in Build* is on; Coolify + withholds it by default to preserve build cache. That setting + (`ApplicationSetting.include_source_commit_in_build`, default `false`) has no + API surface in 4.1.2: it appears in **zero** API controllers, and both the + create and PATCH allowlists in `ApplicationsController.php` (l.914, l.2368) + reject unrecognized keys outright (`"This field is not allowed."`), so cast + cannot smuggle it through — sending it would fail the whole request. Its only + writer is the Livewire *Advanced* tab + (`app/Livewire/Project/Application/Advanced.php:128`), i.e. a human in the UI. + Contrast `connect_to_docker_network`, which *is* in both allowlists and which + `apply` therefore does set on create. So `desiredFromManifest` **warns** once + per dockercompose application (`application builds with dockercompose, + but apply cannot enable "Include Source Commit in Build" on Coolify 4.1.2 …`) + rather than pretending it is desired state. Enable it in the Coolify UI and + redeploy if your image bakes the SHA in at build time. + + **This toggle gates the build-time arg only.** Coolify's **runtime** injection + of `SOURCE_COMMIT` is unconditional with respect to it + (`ApplicationDeploymentJob.php:2949` — `if (! $forBuildTime || …)` + short-circuits true at runtime), so a service that reads + `process.env.SOURCE_COMMIT` per request does **not** need the toggle at all. + What silently suppresses *that* value is an application-level env var of the + same name (`ApplicationDeploymentJob.php:2950`) — a distinct trap, and the + actual cause of a live box reporting `{"sha":"unknown"}`. - **The redis default image is an unverified extrapolation.** Coolify's "New Resource" wizard drives PostgreSQL version selection through a verified `postgres:-alpine` image string; Redis has no version diff --git a/src/resolve.ts b/src/resolve.ts index 43bb888..b02cf7c 100644 --- a/src/resolve.ts +++ b/src/resolve.ts @@ -345,6 +345,34 @@ export function desiredFromManifest( return env; }; for (const [name, app] of Object.entries(envSpec.applications)) { + if (app.build.pack === "dockercompose") { + // Coolify gates the SOURCE_COMMIT *build arg* behind a per-application + // setting — `ApplicationSetting.include_source_commit_in_build`, default + // false — and in 4.1.2 that setting has NO API surface. Verified against + // the v4.1.2 source: it appears in zero API controllers, and both the + // create and the PATCH allowlists in ApplicationsController.php (l.914, + // l.2368) reject unrecognized keys outright ("This field is not + // allowed."), so sending it would fail the whole request rather than be + // quietly ignored. Its only writer is the Livewire Advanced tab + // (app/Livewire/Project/Application/Advanced.php:128) — i.e. a human, in + // the UI. Do NOT add it to `fields` below expecting apply to set it the + // way it sets `connect_to_docker_network` (which *is* in the allowlist, + // which is why that one works): apply would 422 on every run. Warning is + // the only honest move — a manual step the tool knows about and does not + // mention is one that gets forgotten, and this one fails green. + // + // Scope: the toggle gates the BUILD-time arg only. Coolify's *runtime* + // injection of SOURCE_COMMIT is unconditional with respect to it + // (ApplicationDeploymentJob.php:2949 — `if (! $forBuildTime || ...)`, + // which short-circuits true at runtime), so a service that reads + // process.env.SOURCE_COMMIT per request does not need this toggle at all. + // What *does* silently suppress that runtime value is an application-level + // env var of the same name (ApplicationDeploymentJob.php:2950) — a + // different bug, tracked separately. + console.warn( + `application ${name} builds with dockercompose, but apply cannot enable "Include Source Commit in Build" on Coolify 4.1.2 — the setting is absent from the API's field allowlist. If the build consumes SOURCE_COMMIT as a build arg, enable it in the Coolify UI and redeploy; Coolify injects SOURCE_COMMIT at runtime regardless.`, + ); + } desired.push({ kind: "application", name, diff --git a/test/resolve.test.ts b/test/resolve.test.ts index 1cb9de5..101b76a 100644 --- a/test/resolve.test.ts +++ b/test/resolve.test.ts @@ -305,7 +305,9 @@ environments: join(dir, ".infra", "env", "core.prod.env.template"), "PORT=3000\n", ); + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); const { desired } = desiredFromManifest(dir, "prod", {}); + warn.mockRestore(); expect(desired).toHaveLength(1); expect(desired[0]).toMatchObject({ kind: "application", @@ -325,6 +327,56 @@ environments: expect(desired[0].fields).not.toHaveProperty("healthcheck"); expect(desired[0].fields).not.toHaveProperty("domains"); }); + it('warns that apply cannot enable "Include Source Commit in Build" on a dockercompose app (unsettable via the Coolify 4.1.2 API)', () => { + const dir = mkdtempSync(join(tmpdir(), "infra-co-")); + mkdirSync(join(dir, ".infra"), { recursive: true }); + writeFileSync( + join(dir, ".infra", "manifest.yaml"), + `project: widget +environments: + prod: + applications: + core: + source: { repo: acme/widget, branch: main } + build: { pack: dockercompose, base_directory: /, compose_file: docker-compose.yaml } + service_domains: + api: ["https://api.widget.example.com"] +`, + ); + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + const { desired } = desiredFromManifest(dir, "prod", {}); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toMatch(/application core/); + expect(warn.mock.calls[0][0]).toMatch(/Include Source Commit in Build/); + expect(warn.mock.calls[0][0]).toMatch(/Coolify UI/); + warn.mockRestore(); + // The setting is absent from Coolify 4.1.2's create/PATCH allowlists, which + // reject unknown keys outright — so it must never reach `fields`, or apply + // would 422 on every run. Guards the fix a future reader would reach for. + expect(desired[0].fields).not.toHaveProperty( + "include_source_commit_in_build", + ); + }); + it("does not warn about the source-commit toggle for a non-dockercompose app (the build arg is a compose concern)", () => { + const dir = mkdtempSync(join(tmpdir(), "infra-co-")); + mkdirSync(join(dir, ".infra"), { recursive: true }); + writeFileSync( + join(dir, ".infra", "manifest.yaml"), + `project: widget +environments: + prod: + applications: + site: + source: { repo: acme/widget, branch: main } + build: { pack: nixpacks, base_directory: / } + domains: ["https://widget.example.com"] +`, + ); + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + desiredFromManifest(dir, "prod", {}); + expect(warn).not.toHaveBeenCalled(); + warn.mockRestore(); + }); it("throws when the env is missing from the manifest", () => { const dir = mkdtempSync(join(tmpdir(), "infra-co-")); mkdirSync(join(dir, ".infra"), { recursive: true });