Skip to content

fix(cli): make post-deploy-failure hook best-effort and honor proxy.app_port in deploy/rollback - #3143

Merged
bpamiri merged 1 commit into
developfrom
peter/issue-3087-3089-deploy-hooks-appport
Jun 12, 2026
Merged

fix(cli): make post-deploy-failure hook best-effort and honor proxy.app_port in deploy/rollback#3143
bpamiri merged 1 commit into
developfrom
peter/issue-3087-3089-deploy-hooks-appport

Conversation

@bpamiri

@bpamiri bpamiri commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two same-file wheels deploy defects in cli/lucli/services/deploy/cli/DeployMainCli.cfc, both verified to still reproduce on current develop before coding:

#3087 — failing post-deploy-failure hook masked the original deploy error

$fireHook(hooks, "post-deploy-failure", ...) fired unguarded inside the deploy catch block before rethrow, and $fireHook throws DeployMainCli.HookFailed on any non-zero hook exit. A flaky notification hook therefore replaced the real deploy failure.

Fix: the hook call is wrapped in its own try/catch — mirroring the allowFail lock release in the adjacent finally block, which exists for exactly this shadowing reason. The hook still runs (best-effort, still receives KAMAL_ERROR), its non-zero exit is logged as [hook:post-deploy-failure] Hook post-deploy-failure exited with code N (ignored — surfacing the original deploy error), and the original exception rethrows. Exit stays non-zero.

#3089proxy.app_port three-way contradiction, target hardcoded to :3000

deploy() and rollback() hardcoded the kamal-proxy target to <container>:3000, ignoring proxy.app_port entirely (code default 80, docs said 3000, init template scaffolds 8080 — and none of it reached the proxy).

Fix (issue-recommended resolution): both verbs now build the target from config.proxy().appPort(). Default story aligned on Kamal's 80 (the existing Proxy.cfc code default, unchanged); the init template keeps its explicit app_port: 8080 (correct for its generated Dockerfile, which EXPOSEs 8080); docs now agree everywhere — the app_port: 3000 examples in config-reference.mdx and the embedded proxy.md are gone, and the config-reference.mdx caution block documenting this bug is removed.

Red-first evidence

4 new specs failed before the fix with exactly the defect signatures:

Failed - a failing post-deploy-failure hook does not mask the original deploy error (#3087)
    Expected [Wheels.Deploy.RemoteExecutionFailed] but received [DeployMainCli.HookFailed]
Failed - deploy --dry-run builds the kamal-proxy target from proxy.app_port (#3089)
    The needle [--target demo-web-v1:8080] was not found in [... demo-web-v1:3000 ...]
Failed - rollback --dry-run builds the kamal-proxy target from proxy.app_port (#3089)
    ... --target demo-web-v-old:3000 ...
Failed - deploy --dry-run falls back to app_port default 80 when proxy is unconfigured (#3089)

The #3087 specs exec a real failing hook via ProcessBuilder (marker file proves it ran and received KAMAL_ERROR) and assert the [hook:post-deploy-failure] ... exited with code 1 log line via savecontent.

Test evidence (Docker lucee7 harness)

  • DeployMainCliSpec: 43 pass / 0 fail / 0 error (38 existing + 5 new)
  • Full CLI suite: 931 pass / 1 fail / 2 error — the 3 non-passes (SshClientSpec + SshPoolSpec global exceptions, ServerCommandsSpec reload-endpoint) verified identical on pristine baseline (env-dependent, unrelated)
  • Core suite (sqlite): 4445 pass / 12 fail / 0 error — only the 12 tolerated internal.testClientSpec artifacts

Docs updated

  • web/.../deployment/hooks.mdx — event table row, failure-path paragraph, PagerDuty example comment
  • web/.../deployment/config-reference.mdxapp_port bullet now describes the --target wiring; stale :3000 caution removed; example uses 8080
  • web/.../deployment/migrating-from-kamal.mdx — stale "make hooks exit 0" caveat removed
  • Embedded CLI docs hooks.md + proxy.md (wheels deploy docs ...) — same alignment
  • changelog.d/deploy-hook-masking-and-app-port.fixed.md fragment

Fixes #3087 and Fixes #3089

🤖 Generated with Claude Code

…pp_port in deploy/rollback

Two same-file defects in DeployMainCli.cfc:

- #3087: $fireHook(post-deploy-failure) fired unguarded inside the deploy
  catch block, so a flaky notification hook threw DeployMainCli.HookFailed
  and replaced the original deploy error. The hook call is now wrapped in
  its own try/catch (mirroring the allowFail lock release in finally): the
  hook's non-zero exit is logged with the [hook:post-deploy-failure] prefix
  and the original exception rethrows.

- #3089: deploy() and rollback() hardcoded the kamal-proxy target to
  <container>:3000, ignoring proxy.app_port entirely (code default 80,
  init template 8080). Both verbs now build the target from
  config.proxy().appPort(). Default story aligned on Kamal's 80; docs and
  the init template's explicit 8080 (matching its Dockerfile) unchanged.

Docs: hooks.mdx / migrating-from-kamal.mdx / cli docs hooks.md updated to
the shipped best-effort behavior; config-reference.mdx caution block about
the hardcoded :3000 removed and app_port description now states the proxy
target wiring; cli docs proxy.md example no longer suggests 3000.

Specs: 5 new in DeployMainCliSpec (43 specs green), plus a new
with-proxy.yml fixture. CLI suite 931 pass with only the 3 pre-existing
environment-dependent failures (verified identical on baseline); core
suite 4445 pass with only the 12 tolerated testClientSpec artifacts.

Fixes #3087
Fixes #3089

Signed-off-by: Peter Amiri <peter@alurium.com>
@github-actions github-actions Bot added the docs label Jun 12, 2026

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer

TL;DR: This PR fixes two verified defects in cli/lucli/services/deploy/cli/DeployMainCli.cfc — a failing post-deploy-failure hook masking the original deploy error (#3087), and the kamal-proxy target hardcoded to :3000 instead of honoring proxy.app_port (#3089). Both fixes are minimal, correct, red-first tested, and the docs are aligned end-to-end. Verdict: approve.

Correctness (verified, no findings)

I attempted to refute every load-bearing claim against the code at 45b5ff18c; all held up:

  • #3087: the hook call is wrapped in its own try/catch with rethrow outside it (DeployMainCli.cfc:123-131), so the original exception survives. catch (any hookError) also covers non-HookFailed failures (e.g. a ProcessBuilder I/O error), which is strictly better than catching only DeployMainCli.HookFailed. The dry-run path never throws from $fireHook (it only appends to the buffer, DeployMainCli.cfc:494-500), so behavior there is unchanged. Using writeOutput for the hook-failure log is consistent — $fireHook already streams hook output via writeOutput at DeployMainCli.cfc:517.
  • #3089: both deploy() (DeployMainCli.cfc:80,100) and rollback() (:158,165) now build the target from cfg.proxy().appPort(). Config.proxy() returns a default Proxy({}) when the block is absent (Config.cfc:59-64) and Proxy.appPort() defaults to 80 (Proxy.cfc:27-33), so the no-proxy fixture path is safe. redeploy() and setup() delegate to deploy() and inherit the fix. appPort()'s declared numeric return type also means a non-numeric app_port fails at cast rather than flowing into the shell command.

Tests

The 5 new specs assert real contracts, not incidental strings — I cross-checked each assertion against its source:

  • Wheels.Deploy.RemoteExecutionFailed message "Remote command failed on … (exit 1): docker pull …" (FakeSshPool.cfc:73-77) satisfies both toInclude("docker pull") and the marker-file toInclude("exit 1") via KAMAL_ERROR.
  • The logged line is "[hook:post-deploy-failure] Hook post-deploy-failure exited with code 1 (ignored — …)" ($fireHook throw at DeployMainCli.cfc:522-526 plus the new catch), matching both toInclude assertions.
  • --target demo-web-v1:8080 matches ProxyCommands.deploy()'s --target flag (ProxyCommands.cfc:37-43) and container_name()'s service-role-version format (AppCommands.cfc:63-64).
  • FakeSshPool in non-strict mode default-passes the unexpected lock acquire/release commands (FakeSshPool.cfc:93-94), so the real-mode #3087 specs are deterministic.
  • Inline directoryDelete cleanup and fileSetAccessMode(path, "755") follow this spec file's existing prior art (DeployMainCliSpec.cfc:115-133 and the #2671 spec).

Happy path (hook runs, receives KAMAL_ERROR, marker proves execution) and error path (hook exit 1 logged, original error rethrown) are both covered, plus the default-80 fallback.

Docs

All four doc surfaces (guides hooks.mdx / config-reference.mdx / migrating-from-kamal.mdx and the embedded wheels deploy docs hooks.md / proxy.md) now describe the shipped behavior, the stale :3000 caution block is removed, and the changelog fragment changelog.d/deploy-hook-masking-and-app-port.fixed.md uses a valid type. The PR body's test-evidence claims are specific and honest about the 3 pre-existing environment-dependent CLI-suite failures.

Commits

Single commit, header is a valid conventional-commit (fix(cli): …, 95 chars ≤ 100), body explains the why, and the DCO sign-off Signed-off-by: Peter Amiri <peter@alurium.com> matches the git author identity exactly.

@bpamiri
bpamiri merged commit 3a1eb75 into develop Jun 12, 2026
15 checks passed
@bpamiri
bpamiri deleted the peter/issue-3087-3089-deploy-hooks-appport branch June 12, 2026 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

1 participant