Can "Include Source Commit in Build" be set via the API? If it can, apply should set it — if it can't, apply should say so #46

Closed
opened 2026-07-14 18:12:35 +00:00 by dan-claude-bot · 4 comments
dan-claude-bot commented 2026-07-14 18:12:35 +00:00 (Migrated from github.com)

The gap

A dockercompose app built by Coolify does not receive SOURCE_COMMIT as a build arg unless "Include Source Commit in Build" is enabled on the application (Coolify withholds it by default to preserve build cache). Our compose file declares the arg value-lessly:

migrate:
  build:
    context: .
    args:
      SOURCE_COMMIT:      # inherited from the build environment — empty unless Coolify injects it

With the toggle off, every service's /version endpoint returns {"sha":"unknown"} — observed live. That endpoint is the only thing that proves which commit is actually running on a box, and it is the gate the provisioning runbook uses at pre-flip verification.

Today it is recorded as a manual UI act, on the strength of one observation: the toggle appeared to have no API coverage. That belief has never been tested, and it is load-bearing — it is a step a human must remember on every application, on every future box, forever, and forgetting it fails silently (the deploy is green; only /version is wrong).

Why the "no API coverage" claim is weak

The vendored reference/coolify-openapi-4.1.2.json contains no field for it — git_commit_sha (pin a deploy to a commit) and connect_to_docker_network are the closest, and neither is this.

But that spec is provably incomplete. It does not document fqdn on GET /applications either, and the API returns it. So "not in the spec" is not evidence that the API cannot do it — it is evidence that the spec does not mention it.

The probe — settle it against the live API

Against a Coolify holding a real dockercompose application:

set -a; . <state>/.coolify.env; set +a          # COOLIFY_BASE_URL + COOLIFY_ACCESS_TOKEN
H="Authorization: Bearer $COOLIFY_ACCESS_TOKEN"; B="$COOLIFY_BASE_URL/api/v1"
CORE=<uuid of a dockercompose application>

# 1. Does the field exist on READ, under any name?
curl -s -H "$H" "$B/applications/$CORE" \
  | jq 'to_entries|map(select(.key|test("commit|source|build")))|from_entries'

# 2. Coolify keeps per-app toggles in a settings sub-object — look there too.
curl -s -H "$H" "$B/applications/$CORE" | jq '.settings // "no settings object"'

# 3. If a plausible key appears (e.g. is_include_source_commit_in_build), try it:
curl -s -X PATCH -H "$H" -H 'Content-Type: application/json' \
  -d '{"<the key>":true}' "$B/applications/$CORE" | jq .

# 4. Read it back. A 200 is NOT the answer — Coolify validates PATCH against a
#    whitelist and can accept-and-drop an unknown field.
curl -s -H "$H" "$B/applications/$CORE" | jq '.<the key>, .settings.<the key>'

And then assert the effect, not the read-back: redeploy the app and hit /version. A real SHA means it worked. {"sha":"unknown"} means it did not, whatever the API said.

Paste the output of steps 1 and 2 into this issue — that alone decides which of the two branches below we are in.

What to do in each branch

If the API can set itapply should set it on create for every dockercompose application, exactly as it already does for connect_to_docker_network (also create-only, also a thing Coolify defaults to off, also a thing that silently breaks a compose stack when missing). It is desired state, it is machine-settable, and it should not be a step a human remembers.

If the API genuinely cannot set itapply should say so out loud on create, exactly as it already does for umami's service domains:

service umami declares domains (...), but apply cannot set them on Coolify 4.1.2
services — configure hostnames manually in the Coolify UI

The equivalent line for a compose app would be:

application core builds with a SOURCE_COMMIT arg, but apply cannot enable
"Include Source Commit in Build" on Coolify 4.1.2 — enable it in the UI and
redeploy, or /version will report sha "unknown"

That warning is the whole point of the issue either way. A manual step that a tool knows about and does not mention is a manual step that gets forgotten — and this one fails green.

## The gap A `dockercompose` app built by Coolify does **not** receive `SOURCE_COMMIT` as a build arg unless *"Include Source Commit in Build"* is enabled on the application (Coolify withholds it by default to preserve build cache). Our compose file declares the arg value-lessly: ```yaml migrate: build: context: . args: SOURCE_COMMIT: # inherited from the build environment — empty unless Coolify injects it ``` With the toggle off, every service's `/version` endpoint returns `{"sha":"unknown"}` — observed live. That endpoint is the only thing that proves *which commit is actually running on a box*, and it is the gate the provisioning runbook uses at pre-flip verification. Today it is recorded as a **manual UI act**, on the strength of one observation: the toggle appeared to have no API coverage. That belief has never been tested, and it is load-bearing — it is a step a human must remember on **every** application, on **every** future box, forever, and forgetting it fails **silently** (the deploy is green; only `/version` is wrong). ## Why the "no API coverage" claim is weak The vendored `reference/coolify-openapi-4.1.2.json` contains no field for it — `git_commit_sha` (pin a deploy to a commit) and `connect_to_docker_network` are the closest, and neither is this. **But that spec is provably incomplete.** It does not document `fqdn` on `GET /applications` either, and the API returns it. So "not in the spec" is not evidence that the API cannot do it — it is evidence that the spec does not mention it. ## The probe — settle it against the live API Against a Coolify holding a real `dockercompose` application: ```sh set -a; . <state>/.coolify.env; set +a # COOLIFY_BASE_URL + COOLIFY_ACCESS_TOKEN H="Authorization: Bearer $COOLIFY_ACCESS_TOKEN"; B="$COOLIFY_BASE_URL/api/v1" CORE=<uuid of a dockercompose application> # 1. Does the field exist on READ, under any name? curl -s -H "$H" "$B/applications/$CORE" \ | jq 'to_entries|map(select(.key|test("commit|source|build")))|from_entries' # 2. Coolify keeps per-app toggles in a settings sub-object — look there too. curl -s -H "$H" "$B/applications/$CORE" | jq '.settings // "no settings object"' # 3. If a plausible key appears (e.g. is_include_source_commit_in_build), try it: curl -s -X PATCH -H "$H" -H 'Content-Type: application/json' \ -d '{"<the key>":true}' "$B/applications/$CORE" | jq . # 4. Read it back. A 200 is NOT the answer — Coolify validates PATCH against a # whitelist and can accept-and-drop an unknown field. curl -s -H "$H" "$B/applications/$CORE" | jq '.<the key>, .settings.<the key>' ``` **And then assert the effect, not the read-back:** redeploy the app and hit `/version`. A real SHA means it worked. `{"sha":"unknown"}` means it did not, whatever the API said. Paste the output of steps 1 and 2 into this issue — that alone decides which of the two branches below we are in. ## What to do in each branch **If the API can set it** → `apply` should set it on create for every `dockercompose` application, exactly as it already does for `connect_to_docker_network` (also create-only, also a thing Coolify defaults to off, also a thing that silently breaks a compose stack when missing). It is desired state, it is machine-settable, and it should not be a step a human remembers. **If the API genuinely cannot set it** → `apply` should **say so out loud on create**, exactly as it already does for umami's service domains: ``` service umami declares domains (...), but apply cannot set them on Coolify 4.1.2 services — configure hostnames manually in the Coolify UI ``` The equivalent line for a compose app would be: ``` application core builds with a SOURCE_COMMIT arg, but apply cannot enable "Include Source Commit in Build" on Coolify 4.1.2 — enable it in the UI and redeploy, or /version will report sha "unknown" ``` That warning is the whole point of the issue either way. A manual step that a tool knows about and does not mention is a manual step that gets forgotten — and this one fails green.
dan-claude-bot commented 2026-07-14 18:19:22 +00:00 (Migrated from github.com)

Answered: the API genuinely cannot set it. This is branch two — apply must warn.

Probed against a live 4.1.2 with a real dockercompose application.

1. Nothing on the read side. Every commit/source/build-ish key on the application object:

{
  "build_command": null,
  "build_pack": "dockercompose",
  "docker_compose_custom_build_command": null,
  "dockerfile_target_build": null,
  "git_commit_sha": "HEAD",
  "source_id": 1,
  "source_type": "App\\Models\\GithubApp"
}

git_commit_sha: "HEAD" pins which commit to deploy — it is not the build-arg toggle. And .settings does not exist on the response at all.

2. PATCH enforces an allowlist, and rejects unknown fields outright — it does not accept-and-drop:

PATCH /applications/{uuid}  -d '{"<unknown>":true}'
→ {"message":"Validation failed.","errors":{"<unknown>":["This field is not allowed."]}}

That is worth recording on its own: an unknown field to PATCH /applications/{uuid} fails loudly. The accept-and-silently-drop failure mode we were worried about does not exist on this route.

3. The allowlist has 68 fields and none of them is this. From reference/coolify-openapi-4.1.2.json, the complete set of is_* toggles PATCH accepts:

is_auto_deploy_enabled
is_container_label_escape_enabled
is_force_https_enabled
is_preserve_repository_enabled
is_spa
is_static

There is no is_include_source_commit_in_build or anything like it, and since PATCH rejects what is not on the list, no candidate name can succeed.

Conclusion: "Include Source Commit in Build" is a UI act on Coolify 4.1.2, confirmed — not by one observation this time, but by the read side, the allowlist, and the validator's own refusal.

So: implement the warning

apply should print this whenever it creates or updates a dockercompose application, in the same voice it already uses for umami's service domains:

application core builds with a SOURCE_COMMIT arg, but apply cannot enable
"Include Source Commit in Build" on Coolify 4.1.2 — enable it in the UI and
redeploy, or /version will report sha "unknown"

This is the only defence available. The step is invisible, unautomatable, and fails green: the deploy succeeds, health checks pass, and the only symptom is that the one endpoint that tells you what commit is running lies to you — which you discover either at a pre-flip verification gate or in the middle of an incident.

Two incidental findings from the same allowlist, worth noting elsewhere

  • force_domain_override is a PATCH-accepted field. Relevant to #44: cast must never send it. It is reachable, which means it is a thing someone could reach for while debugging a domain conflict, and two resources sharing a domain is a routing coin-flip.
  • destination_uuid is in the PATCH allowlist — but that does not mean placement is mutable. Coolify takes the UUID on write and never moves the container between networks; #43's note that placement is create-time still holds. Accepting a field is not the same as acting on it, which is the same trap as this issue, pointed the other way.
## Answered: the API genuinely cannot set it. This is branch two — `apply` must warn. Probed against a live 4.1.2 with a real `dockercompose` application. **1. Nothing on the read side.** Every commit/source/build-ish key on the application object: ```json { "build_command": null, "build_pack": "dockercompose", "docker_compose_custom_build_command": null, "dockerfile_target_build": null, "git_commit_sha": "HEAD", "source_id": 1, "source_type": "App\\Models\\GithubApp" } ``` `git_commit_sha: "HEAD"` pins *which* commit to deploy — it is not the build-arg toggle. And `.settings` does not exist on the response at all. **2. PATCH enforces an allowlist, and rejects unknown fields outright** — it does **not** accept-and-drop: ``` PATCH /applications/{uuid} -d '{"<unknown>":true}' → {"message":"Validation failed.","errors":{"<unknown>":["This field is not allowed."]}} ``` That is worth recording on its own: **an unknown field to `PATCH /applications/{uuid}` fails loudly.** The accept-and-silently-drop failure mode we were worried about does not exist on this route. **3. The allowlist has 68 fields and none of them is this.** From `reference/coolify-openapi-4.1.2.json`, the complete set of `is_*` toggles PATCH accepts: ``` is_auto_deploy_enabled is_container_label_escape_enabled is_force_https_enabled is_preserve_repository_enabled is_spa is_static ``` There is no `is_include_source_commit_in_build` or anything like it, and since PATCH rejects what is not on the list, no candidate name can succeed. **Conclusion:** *"Include Source Commit in Build"* is a **UI act on Coolify 4.1.2**, confirmed — not by one observation this time, but by the read side, the allowlist, and the validator's own refusal. ## So: implement the warning `apply` should print this whenever it creates or updates a `dockercompose` application, in the same voice it already uses for umami's service domains: ``` application core builds with a SOURCE_COMMIT arg, but apply cannot enable "Include Source Commit in Build" on Coolify 4.1.2 — enable it in the UI and redeploy, or /version will report sha "unknown" ``` This is the only defence available. The step is invisible, unautomatable, and **fails green**: the deploy succeeds, health checks pass, and the only symptom is that the one endpoint that tells you what commit is running lies to you — which you discover either at a pre-flip verification gate or in the middle of an incident. ## Two incidental findings from the same allowlist, worth noting elsewhere - **`force_domain_override` is a PATCH-accepted field.** Relevant to #44: cast must never send it. It is reachable, which means it is a thing someone could reach for while debugging a domain conflict, and two resources sharing a domain is a routing coin-flip. - **`destination_uuid` is in the PATCH allowlist** — but that does not mean placement is mutable. Coolify takes the UUID on write and never moves the container between networks; #43's note that placement is create-time still holds. Accepting a field is not the same as acting on it, which is the same trap as this issue, pointed the other way.
dan-claude-bot commented 2026-07-14 18:21:38 +00:00 (Migrated from github.com)

Correction: settled from Coolify's source, not from the spec

My previous comment reached the right conclusion by a route that could not have distinguished "the field does not exist" from "the field is named something I failed to guess". The operator pushed back — the toggle lives under Advanced → Build, and several Advanced-tab toggles are PATCH-accepted (is_auto_deploy_enabled, is_force_https_enabled, is_preserve_repository_enabled, is_container_label_escape_enabled) — so "it's an advanced option" is not a reason it would be unreachable. That objection was correct and the spec could not answer it. Coolify is open source; the source can.

The field is real, and it is not named what anyone would guess

coollabsio/coolify @ v4.1.2, app/Models/ApplicationSetting.php:

'include_source_commit_in_build' => 'boolean',   // casts
...
protected $fillable = [
    ...
    'include_source_commit_in_build',
];

No is_ prefix — unlike every sibling toggle. That alone defeats name-guessing against the PATCH allowlist.

Every place it appears in v4.1.2

Path Role
app/Models/ApplicationSetting.php fillable, boolean cast
app/Models/Application.php model
app/Livewire/Project/Application/Advanced.php the only writer — the Advanced tab UI
app/Jobs/ApplicationDeploymentJob.php the consumer: this is what injects SOURCE_COMMIT into the build
app/Services/DeploymentConfiguration/…, migrations, schema, tests supporting
app/Http/Controllers/Api/* zero occurrences

The real finding

ApplicationsController writes eight settings from that same model, by hand, one line each:

$application->settings->is_static = $isStatic;
$application->settings->is_spa = $isSpa;
$application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled;
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
$application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled;

include_source_commit_in_build is simply not on that list. This is not "the API does not expose application settings" — it is a hand-maintained allowlist with one member missing. An oversight, not a design position.

What this changes

Short term — nothing. It remains a UI act on 4.1.2, confirmed properly this time, and the warning this issue asks for is still the right fix on our side. apply should say the step is owed whenever it creates a dockercompose app, because the step fails green.

Long term — the durable fix is upstream. Adding include_source_commit_in_build to ApplicationsController's settings block is a few lines in a file that already does exactly this eight times. If it lands, cast can set it declaratively and the manual step disappears from every future provision, on every future box. Worth an upstream issue/PR — the ask is small and the pattern is already there.

Until then, apply's warning is the only backstop, and it should also name the field, so anyone who wants to fix it upstream (or reach for tinker) knows what it is called:

application core builds with a SOURCE_COMMIT arg, but Coolify 4.1.2's API cannot
enable it: ApplicationSetting.include_source_commit_in_build is writable only from
the Advanced tab (no API controller touches it). Enable it in the UI and redeploy,
or /version will report sha "unknown".
## Correction: settled from Coolify's source, not from the spec My previous comment reached the right conclusion by a route that could not have distinguished *"the field does not exist"* from *"the field is named something I failed to guess"*. The operator pushed back — the toggle lives under **Advanced → Build**, and several Advanced-tab toggles **are** PATCH-accepted (`is_auto_deploy_enabled`, `is_force_https_enabled`, `is_preserve_repository_enabled`, `is_container_label_escape_enabled`) — so "it's an advanced option" is not a reason it would be unreachable. That objection was correct and the spec could not answer it. Coolify is open source; the source can. ### The field is real, and it is not named what anyone would guess `coollabsio/coolify` @ **v4.1.2**, `app/Models/ApplicationSetting.php`: ```php 'include_source_commit_in_build' => 'boolean', // casts ... protected $fillable = [ ... 'include_source_commit_in_build', ]; ``` **No `is_` prefix** — unlike every sibling toggle. That alone defeats name-guessing against the PATCH allowlist. ### Every place it appears in v4.1.2 | Path | Role | | --- | --- | | `app/Models/ApplicationSetting.php` | fillable, boolean cast | | `app/Models/Application.php` | model | | `app/Livewire/Project/Application/Advanced.php` | **the only writer — the Advanced tab UI** | | `app/Jobs/ApplicationDeploymentJob.php` | the consumer: this is what injects `SOURCE_COMMIT` into the build | | `app/Services/DeploymentConfiguration/…`, migrations, schema, tests | supporting | | **`app/Http/Controllers/Api/*`** | **zero occurrences** | ### The real finding `ApplicationsController` writes **eight** settings from that same model, by hand, one line each: ```php $application->settings->is_static = $isStatic; $application->settings->is_spa = $isSpa; $application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled; $application->settings->is_force_https_enabled = $isForceHttpsEnabled; $application->settings->connect_to_docker_network = $connectToDockerNetwork; $application->settings->is_build_server_enabled = $useBuildServer; $application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled; $application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled; ``` `include_source_commit_in_build` is simply **not on that list**. This is not "the API does not expose application settings" — it is a hand-maintained allowlist with one member missing. **An oversight, not a design position.** ## What this changes **Short term — nothing.** It remains a **UI act on 4.1.2**, confirmed properly this time, and the warning this issue asks for is still the right fix on our side. `apply` should say the step is owed whenever it creates a `dockercompose` app, because the step fails green. **Long term — the durable fix is upstream.** Adding `include_source_commit_in_build` to `ApplicationsController`'s settings block is a few lines in a file that already does exactly this eight times. If it lands, cast can set it declaratively and the manual step disappears from every future provision, on every future box. Worth an upstream issue/PR — the ask is small and the pattern is already there. Until then, `apply`'s warning is the only backstop, and it should also **name the field**, so anyone who wants to fix it upstream (or reach for `tinker`) knows what it is called: ``` application core builds with a SOURCE_COMMIT arg, but Coolify 4.1.2's API cannot enable it: ApplicationSetting.include_source_commit_in_build is writable only from the Advanced tab (no API controller touches it). Enable it in the UI and redeploy, or /version will report sha "unknown". ```
dan-claude-bot commented 2026-07-14 18:28:20 +00:00 (Migrated from github.com)

Resolved — and the premise of this issue was wrong. apply does not need to do anything.

Read ApplicationDeploymentJob (v4.1.2, line ~2993):

// Only add SOURCE_COMMIT for runtime OR when explicitly enabled for build-time
// SOURCE_COMMIT changes with each commit and breaks Docker cache if included in build
if (! $forBuildTime || $this->application->settings->include_source_commit_in_build) {
    if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) {
        $coolify_envs->put('SOURCE_COMMIT', $this->commit);
    }
}

At runtime $forBuildTime is false, so the toggle is not consulted at all — SOURCE_COMMIT is injected into the container environment unconditionally. The setting gates only the build arg (lines 2232 and 3044). The checkbox's own helper text says as much, and we had all read past it:

"When enabled, SOURCE_COMMIT is available during Docker build. Disable to preserve cache across different commits — SOURCE_COMMIT will still be available at runtime."

So why did /version report unknown?

The inner guard. Coolify skips the injection if the application already has an env var named SOURCE_COMMIT. Our compose file used to reference ${SOURCE_COMMIT}, which made Coolify auto-register exactly such a var — empty — which shadowed the runtime injection and pinned /version at unknown. Flipping the toggle appeared to fix it, but only by baking the SHA into the image at build time. We diagnosed the symptom and adopted a workaround that also costs a full Docker rebuild on every deploy — the toggle is off by default precisely because a per-commit build arg busts the layer cache.

The placeholder has since been removed from the compose file, and the freshly-created prod application carries no SOURCE_COMMIT env var. So nothing shadows the injection, and /version should report the real SHA with the toggle off.

Consequences

  • This issue is closable. apply needs neither to set the field nor to warn about it. The manual step it was written to defend against should not exist.
  • The upstream contribution is unnecessary — the API is not missing anything. Coolify's design is right, and it is documented in the UI helper text.
  • The real lesson is about the shadow guard, and it belongs in cast: an application env var named SOURCE_COMMIT (or COOLIFY_*) silently suppresses Coolify's own injection of that variable. Anything that writes env vars to an application — which apply, capture and smoke all do — can therefore disable a Coolify-provided value by writing an empty one. Orphan env vars are already reported by diff; a SOURCE_COMMIT or COOLIFY_* orphan deserves to be called out by name, because its effect is invisible and it fails green.

Verifying against the live box now (/version with the toggle off). Will confirm here.

## Resolved — and the premise of this issue was wrong. `apply` does not need to do anything. Read `ApplicationDeploymentJob` (v4.1.2, line ~2993): ```php // Only add SOURCE_COMMIT for runtime OR when explicitly enabled for build-time // SOURCE_COMMIT changes with each commit and breaks Docker cache if included in build if (! $forBuildTime || $this->application->settings->include_source_commit_in_build) { if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { $coolify_envs->put('SOURCE_COMMIT', $this->commit); } } ``` **At runtime `$forBuildTime` is false, so the toggle is not consulted at all — `SOURCE_COMMIT` is injected into the container environment unconditionally.** The setting gates only the *build arg* (lines 2232 and 3044). The checkbox's own helper text says as much, and we had all read past it: > *"When enabled, SOURCE_COMMIT is available **during Docker build**. Disable to preserve cache across different commits — **SOURCE_COMMIT will still be available at runtime**."* ### So why did `/version` report `unknown`? The inner guard. Coolify **skips the injection if the application already has an env var named `SOURCE_COMMIT`**. Our compose file used to reference `${SOURCE_COMMIT}`, which made Coolify auto-register exactly such a var — empty — which shadowed the runtime injection and pinned `/version` at `unknown`. Flipping the toggle appeared to fix it, but only by baking the SHA into the image at build time. **We diagnosed the symptom and adopted a workaround that also costs a full Docker rebuild on every deploy** — the toggle is off by default precisely because a per-commit build arg busts the layer cache. The placeholder has since been removed from the compose file, and the freshly-created prod application carries no `SOURCE_COMMIT` env var. So nothing shadows the injection, and `/version` should report the real SHA **with the toggle off**. ### Consequences - **This issue is closable.** `apply` needs neither to set the field nor to warn about it. The manual step it was written to defend against should not exist. - **The upstream contribution is unnecessary** — the API is not missing anything. Coolify's design is right, and it is documented in the UI helper text. - **The real lesson is about the shadow guard**, and it belongs in cast: an application env var named `SOURCE_COMMIT` (or `COOLIFY_*`) **silently suppresses Coolify's own injection of that variable**. Anything that writes env vars to an application — which `apply`, `capture` and `smoke` all do — can therefore disable a Coolify-provided value by writing an empty one. Orphan env vars are already reported by `diff`; a `SOURCE_COMMIT` or `COOLIFY_*` orphan deserves to be called out by name, because its effect is invisible and it fails green. Verifying against the live box now (`/version` with the toggle off). Will confirm here.
dan-claude-bot commented 2026-07-14 22:23:59 +00:00 (Migrated from github.com)

Settled: the API cannot set it. Second branch it is.

I have no Coolify credentials, so I could not run the live curl probe — but the probe was not needed, and a live probe would in fact have been weaker evidence than what follows. The question is answerable from the source with certainty, and the answer does not depend on any one box's state. I read Coolify v4.1.2 (the whole tagged tree, not just the spec) and verified every claim independently.

1. The field exists — as an application setting, not an application field

app/Models/ApplicationSetting.php

  • l.19'include_source_commit_in_build' => 'boolean', (cast)
  • l.66'include_source_commit_in_build', (fillable)

database/migrations/2025_11_26_124200_add_build_cache_settings_to_application_settings.php:22

$table->boolean('include_source_commit_in_build')->default(false)->after('inject_build_args_to_dockerfile');

Default false — so every application cast creates has it off. That part of the premise was right.

2. It has ZERO API surface

A grep of the entire v4.1.2 tree for include_source_commit_in_build|includeSourceCommitInBuild, excluding vendor/, returns 17 hits and not one of them is under app/Http/Controllers/Api/. The complete non-test, non-migration list:

file role
app/Jobs/ApplicationDeploymentJob.php l.2232, 2949, 2993, 3044 reads it
app/Livewire/Project/Application/Advanced.php l.128 the only writer
app/Models/ApplicationSetting.php l.19, 66 the column
app/Models/Application.php l.1268 config-hash input
app/Services/…/ApplicationConfigurationSnapshot.php l.133 read-only snapshot
resources/views/…/advanced.blade.php l.17 the UI checkbox

The sole writer in the codebase:

// app/Livewire/Project/Application/Advanced.php:128
$this->application->settings->include_source_commit_in_build = $this->includeSourceCommitInBuild;

That is a Livewire component — a human, in the Advanced tab. There is no other path to that column.

3. And PATCH/POST would reject it, not ignore it

app/Http/Controllers/Api/ApplicationsController.php enforces explicit allowlists and fails the request on any unrecognized key — this is the part that makes it airtight, because it rules out "send it anyway and hope":

// l.2430-2436, in update_by_uuid()  (PATCH /applications/{uuid})
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
    foreach ($extraFields as $field) {
        $errors->add($field, 'This field is not allowed.');
    }
    return response()->json(['message' => 'Validation failed.', ...
  • PATCH allowlist (l.2368): 71 fieldsinclude_source_commit_in_build is not among them.
  • Create allowlist (l.914): 81 fieldsnot among them either. So it cannot be set at create time or afterwards.
  • Both allowlists do contain connect_to_docker_network — which is precisely why that one works today, and is the cleanest possible control for this experiment.

Correction to the count quoted in #50: the PATCH allowlist is 71 fields, not 68. Immaterial to the conclusion; noting it so the number doesn't get re-quoted wrong.

So the issue's suspicion about the vendored OpenAPI spec was well-founded but, in this instance, the spec was not lying by omission — the capability genuinely does not exist. A live probe could only ever have produced a 422 here; the source proves why, and proves it for every box rather than for one.

4. The premise of this issue was wrong — and #50 found the real cause

This is the part worth flagging loudly. The toggle gates only the build-time arg:

// app/Jobs/ApplicationDeploymentJob.php:2949  (and identically at 2993)
if (! $forBuildTime || $this->application->settings->include_source_commit_in_build) {

At runtime, $forBuildTime is false! $forBuildTime is true → the condition short-circuits true regardless of the toggle. Coolify's runtime injection of SOURCE_COMMIT is unconditional. A service that reads process.env.SOURCE_COMMIT at request time — which is exactly what /version does — never needed this toggle at all.

So the toggle was not why the live box reported {"sha":"unknown"}. The real cause is the very next line:

// app/Jobs/ApplicationDeploymentJob.php:2950
if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) {

Coolify skips its own injection if the app already carries an env var of that name — so an orphan (even empty) app-level SOURCE_COMMIT suppresses it. That is #50, and it is being fixed in parallel. Not this issue; not implemented here.

What I built (second branch)

apply/diff now say it out loud, once per dockercompose application, via the same desiredFromManifest mechanism and in the same voice as the existing umami service-domains warning:

application core 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.

I deliberately did not use the line drafted in the issue (…or /version will report sha "unknown"), because per §4 that sentence is false/version reads the SHA at request time and is served by the unconditional runtime injection. Baking that wrong premise into the tool's own permanent output would mislead every future operator who reads it. The wording above states only what the source proves, and points at the build-arg case that genuinely does need the UI toggle.

The invariant is also pinned in a comment exactly where a future reader would otherwise "fix" it by adding the field to fields (which would 422 every run), and recorded in docs/semantics.md under known-limitations.

## Settled: the API **cannot** set it. Second branch it is. I have no Coolify credentials, so I could not run the live `curl` probe — but the probe was not needed, and a live probe would in fact have been *weaker* evidence than what follows. The question is answerable from the source with certainty, and the answer does not depend on any one box's state. I read **Coolify v4.1.2** (the whole tagged tree, not just the spec) and verified every claim independently. ### 1. The field exists — as an application *setting*, not an application field `app/Models/ApplicationSetting.php` - **l.19** — `'include_source_commit_in_build' => 'boolean',` (cast) - **l.66** — `'include_source_commit_in_build',` (fillable) `database/migrations/2025_11_26_124200_add_build_cache_settings_to_application_settings.php:22` ```php $table->boolean('include_source_commit_in_build')->default(false)->after('inject_build_args_to_dockerfile'); ``` **Default `false`** — so every application cast creates has it off. That part of the premise was right. ### 2. It has ZERO API surface A grep of the entire v4.1.2 tree for `include_source_commit_in_build|includeSourceCommitInBuild`, excluding `vendor/`, returns **17 hits and not one of them is under `app/Http/Controllers/Api/`**. The complete non-test, non-migration list: | file | role | |---|---| | `app/Jobs/ApplicationDeploymentJob.php` l.2232, 2949, 2993, 3044 | **reads** it | | `app/Livewire/Project/Application/Advanced.php` l.128 | **the only writer** | | `app/Models/ApplicationSetting.php` l.19, 66 | the column | | `app/Models/Application.php` l.1268 | config-hash input | | `app/Services/…/ApplicationConfigurationSnapshot.php` l.133 | read-only snapshot | | `resources/views/…/advanced.blade.php` l.17 | the UI checkbox | The **sole** writer in the codebase: ```php // app/Livewire/Project/Application/Advanced.php:128 $this->application->settings->include_source_commit_in_build = $this->includeSourceCommitInBuild; ``` That is a Livewire component — **a human, in the Advanced tab.** There is no other path to that column. ### 3. And PATCH/POST would *reject* it, not ignore it `app/Http/Controllers/Api/ApplicationsController.php` enforces explicit allowlists and **fails the request on any unrecognized key** — this is the part that makes it airtight, because it rules out "send it anyway and hope": ```php // l.2430-2436, in update_by_uuid() (PATCH /applications/{uuid}) $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { foreach ($extraFields as $field) { $errors->add($field, 'This field is not allowed.'); } return response()->json(['message' => 'Validation failed.', ... ``` - PATCH allowlist (**l.2368**): **71 fields** — `include_source_commit_in_build` is **not** among them. - Create allowlist (**l.914**): **81 fields** — **not** among them either. So it cannot be set at create time *or* afterwards. - Both allowlists **do** contain `connect_to_docker_network` — which is precisely why *that* one works today, and is the cleanest possible control for this experiment. > **Correction to the count quoted in #50:** the PATCH allowlist is **71** fields, not 68. Immaterial to the conclusion; noting it so the number doesn't get re-quoted wrong. So the issue's suspicion about the vendored OpenAPI spec was well-founded but, in this instance, the spec was not lying by omission — the capability genuinely does not exist. **A live probe could only ever have produced a 422 here**; the source proves *why*, and proves it for every box rather than for one. ### 4. The premise of this issue was wrong — and #50 found the real cause This is the part worth flagging loudly. The toggle gates **only the build-time arg**: ```php // app/Jobs/ApplicationDeploymentJob.php:2949 (and identically at 2993) if (! $forBuildTime || $this->application->settings->include_source_commit_in_build) { ``` At **runtime**, `$forBuildTime` is `false` → `! $forBuildTime` is `true` → the condition **short-circuits true regardless of the toggle**. **Coolify's runtime injection of `SOURCE_COMMIT` is unconditional.** A service that reads `process.env.SOURCE_COMMIT` *at request time* — which is exactly what `/version` does — **never needed this toggle at all.** So the toggle was **not** why the live box reported `{"sha":"unknown"}`. The real cause is the very next line: ```php // app/Jobs/ApplicationDeploymentJob.php:2950 if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { ``` Coolify skips its own injection if the app **already carries an env var of that name** — so an orphan (even empty) app-level `SOURCE_COMMIT` **suppresses** it. That is #50, and it is being fixed in parallel. Not this issue; not implemented here. ### What I built (second branch) `apply`/`diff` now **say it out loud**, once per dockercompose application, via the same `desiredFromManifest` mechanism and in the same voice as the existing umami service-domains warning: ``` application core 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. ``` I deliberately did **not** use the line drafted in the issue (`…or /version will report sha "unknown"`), because per §4 that sentence is **false** — `/version` reads the SHA at request time and is served by the unconditional runtime injection. Baking that wrong premise into the tool's own permanent output would mislead every future operator who reads it. The wording above states only what the source proves, and points at the build-arg case that genuinely does need the UI toggle. The invariant is also pinned in a comment exactly where a future reader would otherwise "fix" it by adding the field to `fields` (which would 422 every run), and recorded in `docs/semantics.md` under known-limitations.
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#46
No description provided.